diff --git a/backend-dev/docker-compose.yml b/backend-dev/docker-compose.yml index 31869c774d..f9d34fc5d1 100644 --- a/backend-dev/docker-compose.yml +++ b/backend-dev/docker-compose.yml @@ -1,3 +1,4 @@ +# name: apf services: db: # specify container name to make it easier to run commands @@ -36,8 +37,7 @@ services: - db env_file: - ./.env - command: - [ + command: [ '--connection', '${DATABASE_URL}', '--schema', @@ -57,6 +57,9 @@ services: '--enable-query-batching', '--retry-on-init-fail', '--enhance-graphiql', + # enable exporting schema for apollo, only in dev + # '--export-schema-json', + # '/my_data/schema.json', ] volumes: db_data: diff --git a/backend-dev/graphql/Dockerfile b/backend-dev/graphql/Dockerfile index 75986e3d6d..b5de3c64ba 100644 --- a/backend-dev/graphql/Dockerfile +++ b/backend-dev/graphql/Dockerfile @@ -10,6 +10,10 @@ RUN npm install -g postgraphile-plugin-connection-filter-postgis COPY --chown=node:node .postgraphilerc.js .postgraphilerc.js +# enable exporting schema.json, only in dev +# WORKDIR /my_data +# RUN chown -R node:node /my_data + EXPOSE 5000 USER node diff --git a/backend/docker-compose.yml b/backend/docker-compose.yml index 26d4f29364..bebde0d34d 100644 --- a/backend/docker-compose.yml +++ b/backend/docker-compose.yml @@ -1,3 +1,4 @@ +# name: apf services: db: # specify container name to make it easier to run commands diff --git a/backend/graphql/Dockerfile b/backend/graphql/Dockerfile index 73de58e55f..8210e78397 100644 --- a/backend/graphql/Dockerfile +++ b/backend/graphql/Dockerfile @@ -2,7 +2,7 @@ FROM node:slim # RUN apk add dumb-init RUN apt-get update && apt-get install -y dumb-init -ENV NODE_ENV production +ENV NODE_ENV=production RUN npm install -g postgraphile RUN npm install -g postgraphile-plugin-connection-filter diff --git a/package-lock.json b/package-lock.json index d3955256a8..ee8085c624 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "apflora", - "version": "1.114.5", + "version": "1.114.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "apflora", - "version": "1.114.5", + "version": "1.114.7", "license": "ISC", "dependencies": { - "@apollo/client": "3.12.2", + "@apollo/client": "3.12.3", "@cyntler/react-doc-viewer": "1.17.0", "@dnd-kit/core": "6.3.1", "@dnd-kit/sortable": "10.0.0", @@ -18,10 +18,10 @@ "@fontsource/roboto": "5.1.0", "@fontsource/roboto-mono": "5.1.0", "@json2csv/plainjs": "7.0.6", - "@mui/lab": "6.0.0-beta.19", - "@mui/material": "6.1.10", + "@mui/lab": "6.0.0-beta.20", + "@mui/material": "6.2.1", "@react-leaflet/core": "2.1.0", - "@tanstack/react-query": "5.62.7", + "@tanstack/react-query": "5.62.8", "@turf/ellipse": "7.1.0", "@turf/helpers": "7.1.0", "@turf/nearest-point": "7.1.0", @@ -34,10 +34,10 @@ "dexie": "4.0.10", "exceljs": "4.4.0", "file-saver": "2.0.5", - "framer-motion": "11.14.0", + "framer-motion": "11.15.0", "get-urls": "12.1.0", "is-uuid": "1.0.2", - "jotai": "2.10.3", + "jotai": "2.10.4", "js-file-download": "0.4.12", "jwt-decode": "4.0.0", "leaflet": "1.9.4", @@ -58,12 +58,12 @@ "proj4": "2.15.0", "proj4leaflet": "1.0.2", "query-string": "9.1.1", - "react": "18.3.1", + "react": "19.0.0", "react-copy-to-clipboard": "5.1.0", "react-datepicker": "4.25.0", "react-dnd": "16.0.1", "react-dnd-html5-backend": "16.0.1", - "react-dom": "18.3.1", + "react-dom": "19.0.0", "react-error-boundary": "4.1.2", "react-highlight-words": "0.20.0", "react-icons": "5.4.0", @@ -76,9 +76,10 @@ "react-select": "5.9.0", "react-split-pane": "0.1.92", "react-stylable-diff": "2.0.0", + "react-transition-group": "4.4.5", "react-typography": "0.16.23", - "react-window": "1.8.10", - "recharts": "2.14.1", + "react-window": "1.8.11", + "recharts": "2.15.0", "redaxios": "0.5.1", "screenfull": "6.0.2", "typography": "0.16.24", @@ -89,7 +90,7 @@ "@emotion/babel-plugin": "11.13.5", "@eslint/compat": "1.2.4", "@eslint/eslintrc": "3.2.0", - "@eslint/js": "9.16.0", + "@eslint/js": "9.17.0", "@playwright/test": "1.49.1", "@types/leaflet": "1.9.15", "@types/leaflet-draw": "1.0.11", @@ -97,29 +98,29 @@ "@types/node": "22.10.2", "@types/proj4": "2.5.5", "@types/proj4leaflet": "1.0.10", - "@types/react": "18.3.14", + "@types/react": "19.0.2", "@types/react-datepicker": "4.19.6", - "@types/react-dom": "18.3.2", + "@types/react-dom": "19.0.2", "@types/react-linkify": "1.0.4", - "@typescript-eslint/eslint-plugin": "8.18.0", - "@typescript-eslint/parser": "8.18.0", + "@typescript-eslint/eslint-plugin": "8.18.1", + "@typescript-eslint/parser": "8.18.1", "@vitejs/plugin-react": "4.3.4", "@vitejs/plugin-react-swc": "3.7.2", "copyfiles": "2.4.1", "cross-env": "7.0.3", - "cypress": "13.16.1", + "cypress": "13.17.0", "emotion-swc-plugin": "0.0.5", - "eslint": "9.16.0", + "eslint": "9.17.0", "eslint-plugin-import": "2.31.0", "eslint-plugin-react-hooks": "5.1.0", "eslint-plugin-react-refresh": "0.4.16", - "globals": "15.13.0", + "globals": "15.14.0", "prettier": "3.4.2", "source-map-explorer": "2.5.3", "tslint-config-prettier": "1.18.0", "typescript": "5.7.2", - "vercel": "39.2.0", - "vite": "6.0.3", + "vercel": "39.2.2", + "vite": "6.0.4", "vite-plugin-pwa": "0.21.1", "vite-plugin-svgr": "4.3.0" } @@ -139,9 +140,9 @@ } }, "node_modules/@apollo/client": { - "version": "3.12.2", - "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.12.2.tgz", - "integrity": "sha512-dkacsdMgVsrrQhLpN4JqZTIEfnNsPVwny+4vccSRqheWZElzUz1Xi0h39p2+TieS1f+wwvyzwpoJEV57vwzT9Q==", + "version": "3.12.3", + "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.12.3.tgz", + "integrity": "sha512-KZ5zymRdb8bMbGUb1wP2U04ff7qIGgaC1BCdCVC+IPFiXkxEhHBc5fDEQOwAUT+vUo9KbBh3g7QK/JCOswn59w==", "license": "MIT", "dependencies": { "@graphql-typed-document-node/core": "^3.1.1", @@ -1881,9 +1882,9 @@ } }, "node_modules/@cypress/request": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.6.tgz", - "integrity": "sha512-fi0eVdCOtKu5Ed6+E8mYxUF6ZTFJDZvHogCBelM0xVXmrDEkyM22gRArQzq1YcHPm1V47Vf/iAD+WgVdUlJCGg==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.7.tgz", + "integrity": "sha512-LzxlLEMbBOPYB85uXrDqvD4MgcenjRBLIns3zyhx7vTPj/0u2eQhzXvPiGcaJrV38Q9dbkExWp6cOHPJ+EtFYg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1900,7 +1901,7 @@ "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "6.13.0", + "qs": "6.13.1", "safe-buffer": "^5.1.2", "tough-cookie": "^5.0.0", "tunnel-agent": "^0.6.0", @@ -2734,9 +2735,9 @@ "license": "MIT" }, "node_modules/@eslint/js": { - "version": "9.16.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz", - "integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", + "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", "dev": true, "license": "MIT", "engines": { @@ -2943,9 +2944,9 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -3052,15 +3053,15 @@ } }, "node_modules/@mui/base": { - "version": "5.0.0-beta.66", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.66.tgz", - "integrity": "sha512-1SzcNbtIms0o/Dx+599B6QbvR5qUMBUjwc2Gs47h1HsF7RcEFXxqaq7zrWkIWbvGctIIPx0j330oGx/SkF+UmA==", + "version": "5.0.0-beta.67", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.67.tgz", + "integrity": "sha512-pwjibXqBrSw1Z1fZEIhWJnMJHSvh3BLz0pZcrX/x01B/BVf6QUbFlGVV/k8KCsZ9sPzln1l8bBJ+ynIIE9LtjA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", "@floating-ui/react-dom": "^2.1.1", - "@mui/types": "^7.2.19", - "@mui/utils": "^6.2.0", + "@mui/types": "^7.2.20", + "@mui/utils": "^6.2.1", "@popperjs/core": "^2.11.8", "clsx": "^2.1.1", "prop-types": "^15.8.1" @@ -3084,9 +3085,9 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "6.1.10", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.10.tgz", - "integrity": "sha512-LY5wdiLCBDY7u+Od8UmFINZFGN/5ZU90fhAslf/ZtfP+5RhuY45f679pqYIxe0y54l6Gkv9PFOc8Cs10LDTBYg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.2.1.tgz", + "integrity": "sha512-U/8vS1+1XiHBnnRRESSG1gvr6JDHdPjrpnW6KEebkAQWBn6wrpbSF/XSZ8/vJIRXH5NyDmMHi4Ro5Q70//JKhA==", "license": "MIT", "funding": { "type": "opencollective", @@ -3094,16 +3095,16 @@ } }, "node_modules/@mui/lab": { - "version": "6.0.0-beta.19", - "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-beta.19.tgz", - "integrity": "sha512-t7iub8kjpLdA5uDGwGnNRjtGc1vYEUnDwSROjKrnYqjOlCQhBajFa8uoQtaST6jE/VEk6cxpDMnN5MalC6YpCg==", + "version": "6.0.0-beta.20", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-beta.20.tgz", + "integrity": "sha512-Sg3aTrSSsfXcSHwKhnZbXmz7kUZzvXww3vO4p8U/DbMVSGJxll1GRpKNQkFLpZy2y7ZBTgKaZTbPYw4Q6UKrsA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/base": "5.0.0-beta.66", - "@mui/system": "^6.2.0", - "@mui/types": "^7.2.19", - "@mui/utils": "^6.2.0", + "@mui/base": "5.0.0-beta.67", + "@mui/system": "^6.2.1", + "@mui/types": "^7.2.20", + "@mui/utils": "^6.2.1", "clsx": "^2.1.1", "prop-types": "^15.8.1" }, @@ -3117,8 +3118,8 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material": "^6.2.0", - "@mui/material-pigment-css": "^6.2.0", + "@mui/material": "^6.2.1", + "@mui/material-pigment-css": "^6.2.1", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -3139,22 +3140,22 @@ } }, "node_modules/@mui/material": { - "version": "6.1.10", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.10.tgz", - "integrity": "sha512-txnwYObY4N9ugv5T2n5h1KcbISegZ6l65w1/7tpSU5OB6MQCU94YkP8n/3slDw2KcEfRk4+4D8EUGfhSPMODEQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.2.1.tgz", + "integrity": "sha512-7VlKGsRKsy1bOSOPaSNgpkzaL+0C7iWAVKd2KYyAvhR9fTLJtiAMpq+KuzgEh1so2mtvQERN0tZVIceWMiIesw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/core-downloads-tracker": "^6.1.10", - "@mui/system": "^6.1.10", - "@mui/types": "^7.2.19", - "@mui/utils": "^6.1.10", + "@mui/core-downloads-tracker": "^6.2.1", + "@mui/system": "^6.2.1", + "@mui/types": "^7.2.20", + "@mui/utils": "^6.2.1", "@popperjs/core": "^2.11.8", - "@types/react-transition-group": "^4.4.11", + "@types/react-transition-group": "^4.4.12", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1", - "react-is": "^18.3.1", + "react-is": "^19.0.0", "react-transition-group": "^4.4.5" }, "engines": { @@ -3167,7 +3168,7 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.1.10", + "@mui/material-pigment-css": "^6.2.1", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -3188,13 +3189,13 @@ } }, "node_modules/@mui/private-theming": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.2.0.tgz", - "integrity": "sha512-lYd2MrVddhentF1d/cMXKnwlDjr/shbO3A2eGq22PCYUoZaqtAGZMc0U86KnJ/Sh5YzNYePqTOaaowAN8Qea8A==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.2.1.tgz", + "integrity": "sha512-u1y0gpcfrRRxCcIdVeU5eIvkinA82Q8ft178WUNYuoFQrsOrXdlBdZlRVi+eYuUFp1iXI55Cud7sMZZtETix5Q==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/utils": "^6.2.0", + "@mui/utils": "^6.2.1", "prop-types": "^15.8.1" }, "engines": { @@ -3215,9 +3216,9 @@ } }, "node_modules/@mui/styled-engine": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.2.0.tgz", - "integrity": "sha512-rV4YCu6kcCjMnHFXU/tQcL6XfYVfFVR8n3ZVNGnk2rpXnt/ctOPJsF+eUQuhkHciueLVKpI06+umr1FxWWhVmQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.2.1.tgz", + "integrity": "sha512-6R3OgYw6zgCZWFYYMfxDqpGfJA78mUTOIlUDmmJlr60ogVNCrM87X0pqx5TbZ2OwUyxlJxN9qFgRr+J9H6cOBg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", @@ -3249,16 +3250,16 @@ } }, "node_modules/@mui/system": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.2.0.tgz", - "integrity": "sha512-DCeqev9Cd4f4pm3O1lqSGW/DIHHBG6ZpqMX9iIAvN4asYv+pPWv2/lKov9kWk5XThhxFnGSv93SRNE1kNRRg5Q==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.2.1.tgz", + "integrity": "sha512-0lc8CbBP4WAAF+SmGMFJI9bpIyQvW3zvwIDzLsb26FIB/4Z0pO7qGe8mkAl0RM63Vb37899qxnThhHKgAAdy6w==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/private-theming": "^6.2.0", - "@mui/styled-engine": "^6.2.0", - "@mui/types": "^7.2.19", - "@mui/utils": "^6.2.0", + "@mui/private-theming": "^6.2.1", + "@mui/styled-engine": "^6.2.1", + "@mui/types": "^7.2.20", + "@mui/utils": "^6.2.1", "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1" @@ -3289,9 +3290,9 @@ } }, "node_modules/@mui/types": { - "version": "7.2.19", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.19.tgz", - "integrity": "sha512-6XpZEM/Q3epK9RN8ENoXuygnqUQxE+siN/6rGRi2iwJPgBUR25mphYQ9ZI87plGh58YoZ5pp40bFvKYOCDJ3tA==", + "version": "7.2.20", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.20.tgz", + "integrity": "sha512-straFHD7L8v05l/N5vcWk+y7eL9JF0C2mtph/y4BPm3gn2Eh61dDwDB65pa8DLss3WJfDXYC7Kx5yjP0EmXpgw==", "license": "MIT", "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" @@ -3303,13 +3304,13 @@ } }, "node_modules/@mui/utils": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.2.0.tgz", - "integrity": "sha512-77CaFJi+OIi2SjbPwCis8z5DXvE0dfx9hBz5FguZHt1VYFlWEPCWTHcMsQCahSErnfik5ebLsYK8+D+nsjGVfw==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.2.1.tgz", + "integrity": "sha512-ubLqGIMhKUH2TF/Um+wRzYXgAooQw35th+DPemGrTpgrZHpOgcnUDIDbwsk1e8iQiuJ3mV/ErTtcQrecmlj5cg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.26.0", - "@mui/types": "^7.2.19", + "@mui/types": "^7.2.20", "@types/prop-types": "^15.7.14", "clsx": "^2.1.1", "prop-types": "^15.8.1", @@ -3332,12 +3333,6 @@ } } }, - "node_modules/@mui/utils/node_modules/react-is": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", - "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==", - "license": "MIT" - }, "node_modules/@next/env": { "version": "14.2.20", "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.20.tgz", @@ -3592,9 +3587,9 @@ } }, "node_modules/@rollup/plugin-node-resolve": { - "version": "15.3.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.0.tgz", - "integrity": "sha512-9eO5McEICxMzJpDW9OnMYSv4Sta3hmt7VtBFz5zR9273suNOydOyq/FrGeGy+KsTRFm8w0SLVhzig2ILFT63Ag==", + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", + "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", "dev": true, "license": "MIT", "dependencies": { @@ -3617,9 +3612,9 @@ } }, "node_modules/@rollup/plugin-node-resolve/node_modules/@rollup/pluginutils": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz", - "integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3690,9 +3685,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.0.tgz", - "integrity": "sha512-wLJuPLT6grGZsy34g4N1yRfYeouklTgPhH1gWXCYspenKYD0s3cR99ZevOGw5BexMNywkbV3UkjADisozBmpPQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.28.1.tgz", + "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==", "cpu": [ "arm" ], @@ -3704,9 +3699,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.0.tgz", - "integrity": "sha512-eiNkznlo0dLmVG/6wf+Ifi/v78G4d4QxRhuUl+s8EWZpDewgk7PX3ZyECUXU0Zq/Ca+8nU8cQpNC4Xgn2gFNDA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.28.1.tgz", + "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==", "cpu": [ "arm64" ], @@ -3718,9 +3713,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.0.tgz", - "integrity": "sha512-lmKx9yHsppblnLQZOGxdO66gT77bvdBtr/0P+TPOseowE7D9AJoBw8ZDULRasXRWf1Z86/gcOdpBrV6VDUY36Q==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.28.1.tgz", + "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==", "cpu": [ "arm64" ], @@ -3732,9 +3727,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.0.tgz", - "integrity": "sha512-8hxgfReVs7k9Js1uAIhS6zq3I+wKQETInnWQtgzt8JfGx51R1N6DRVy3F4o0lQwumbErRz52YqwjfvuwRxGv1w==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.28.1.tgz", + "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==", "cpu": [ "x64" ], @@ -3746,9 +3741,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.0.tgz", - "integrity": "sha512-lA1zZB3bFx5oxu9fYud4+g1mt+lYXCoch0M0V/xhqLoGatbzVse0wlSQ1UYOWKpuSu3gyN4qEc0Dxf/DII1bhQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.28.1.tgz", + "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==", "cpu": [ "arm64" ], @@ -3760,9 +3755,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.0.tgz", - "integrity": "sha512-aI2plavbUDjCQB/sRbeUZWX9qp12GfYkYSJOrdYTL/C5D53bsE2/nBPuoiJKoWp5SN78v2Vr8ZPnB+/VbQ2pFA==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.28.1.tgz", + "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==", "cpu": [ "x64" ], @@ -3774,9 +3769,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.0.tgz", - "integrity": "sha512-WXveUPKtfqtaNvpf0iOb0M6xC64GzUX/OowbqfiCSXTdi/jLlOmH0Ba94/OkiY2yTGTwteo4/dsHRfh5bDCZ+w==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.28.1.tgz", + "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==", "cpu": [ "arm" ], @@ -3788,9 +3783,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.0.tgz", - "integrity": "sha512-yLc3O2NtOQR67lI79zsSc7lk31xjwcaocvdD1twL64PK1yNaIqCeWI9L5B4MFPAVGEVjH5k1oWSGuYX1Wutxpg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.28.1.tgz", + "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==", "cpu": [ "arm" ], @@ -3802,9 +3797,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.0.tgz", - "integrity": "sha512-+P9G9hjEpHucHRXqesY+3X9hD2wh0iNnJXX/QhS/J5vTdG6VhNYMxJ2rJkQOxRUd17u5mbMLHM7yWGZdAASfcg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.28.1.tgz", + "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==", "cpu": [ "arm64" ], @@ -3816,9 +3811,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.0.tgz", - "integrity": "sha512-1xsm2rCKSTpKzi5/ypT5wfc+4bOGa/9yI/eaOLW0oMs7qpC542APWhl4A37AENGZ6St6GBMWhCCMM6tXgTIplw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.28.1.tgz", + "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==", "cpu": [ "arm64" ], @@ -3829,10 +3824,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.28.1.tgz", + "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.0.tgz", - "integrity": "sha512-zgWxMq8neVQeXL+ouSf6S7DoNeo6EPgi1eeqHXVKQxqPy1B2NvTbaOUWPn/7CfMKL7xvhV0/+fq/Z/J69g1WAQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.28.1.tgz", + "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==", "cpu": [ "ppc64" ], @@ -3844,9 +3853,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.0.tgz", - "integrity": "sha512-VEdVYacLniRxbRJLNtzwGt5vwS0ycYshofI7cWAfj7Vg5asqj+pt+Q6x4n+AONSZW/kVm+5nklde0qs2EUwU2g==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.28.1.tgz", + "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==", "cpu": [ "riscv64" ], @@ -3858,9 +3867,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.0.tgz", - "integrity": "sha512-LQlP5t2hcDJh8HV8RELD9/xlYtEzJkm/aWGsauvdO2ulfl3QYRjqrKW+mGAIWP5kdNCBheqqqYIGElSRCaXfpw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.28.1.tgz", + "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==", "cpu": [ "s390x" ], @@ -3872,9 +3881,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.0.tgz", - "integrity": "sha512-Nl4KIzteVEKE9BdAvYoTkW19pa7LR/RBrT6F1dJCV/3pbjwDcaOq+edkP0LXuJ9kflW/xOK414X78r+K84+msw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.28.1.tgz", + "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==", "cpu": [ "x64" ], @@ -3886,9 +3895,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.0.tgz", - "integrity": "sha512-eKpJr4vBDOi4goT75MvW+0dXcNUqisK4jvibY9vDdlgLx+yekxSm55StsHbxUsRxSTt3JEQvlr3cGDkzcSP8bw==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.28.1.tgz", + "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==", "cpu": [ "x64" ], @@ -3900,9 +3909,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.0.tgz", - "integrity": "sha512-Vi+WR62xWGsE/Oj+mD0FNAPY2MEox3cfyG0zLpotZdehPFXwz6lypkGs5y38Jd/NVSbOD02aVad6q6QYF7i8Bg==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.28.1.tgz", + "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==", "cpu": [ "arm64" ], @@ -3914,9 +3923,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.0.tgz", - "integrity": "sha512-kN/Vpip8emMLn/eOza+4JwqDZBL6MPNpkdaEsgUtW1NYN3DZvZqSQrbKzJcTL6hd8YNmFTn7XGWMwccOcJBL0A==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.28.1.tgz", + "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==", "cpu": [ "ia32" ], @@ -3928,9 +3937,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.0.tgz", - "integrity": "sha512-Bvno2/aZT6usSa7lRDL2+hMjVAGjuqaymF1ApZm31JXzniR/hvr14jpU+/z4X6Gt5BPlzosscyJZGUvguXIqeQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.28.1.tgz", + "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==", "cpu": [ "x64" ], @@ -4227,9 +4236,9 @@ } }, "node_modules/@swc/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.0.tgz", - "integrity": "sha512-+CuuTCmQFfzaNGg1JmcZvdUVITQXJk9sMnl1C2TiDLzOSVOJRwVD4dNo5dljX/qxpMAN+2BIYlwjlSkoGi6grg==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.1.tgz", + "integrity": "sha512-rQ4dS6GAdmtzKiCRt3LFVxl37FaY1cgL9kSUTnhQ2xc3fmHOd7jdJK/V4pSZMG1ruGTd0bsi34O2R0Olg9Zo/w==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -4245,16 +4254,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.10.0", - "@swc/core-darwin-x64": "1.10.0", - "@swc/core-linux-arm-gnueabihf": "1.10.0", - "@swc/core-linux-arm64-gnu": "1.10.0", - "@swc/core-linux-arm64-musl": "1.10.0", - "@swc/core-linux-x64-gnu": "1.10.0", - "@swc/core-linux-x64-musl": "1.10.0", - "@swc/core-win32-arm64-msvc": "1.10.0", - "@swc/core-win32-ia32-msvc": "1.10.0", - "@swc/core-win32-x64-msvc": "1.10.0" + "@swc/core-darwin-arm64": "1.10.1", + "@swc/core-darwin-x64": "1.10.1", + "@swc/core-linux-arm-gnueabihf": "1.10.1", + "@swc/core-linux-arm64-gnu": "1.10.1", + "@swc/core-linux-arm64-musl": "1.10.1", + "@swc/core-linux-x64-gnu": "1.10.1", + "@swc/core-linux-x64-musl": "1.10.1", + "@swc/core-win32-arm64-msvc": "1.10.1", + "@swc/core-win32-ia32-msvc": "1.10.1", + "@swc/core-win32-x64-msvc": "1.10.1" }, "peerDependencies": { "@swc/helpers": "*" @@ -4266,9 +4275,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.0.tgz", - "integrity": "sha512-wCeUpanqZyzvgqWRtXIyhcFK3CqukAlYyP+fJpY2gWc/+ekdrenNIfZMwY7tyTFDkXDYEKzvn3BN/zDYNJFowQ==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.1.tgz", + "integrity": "sha512-NyELPp8EsVZtxH/mEqvzSyWpfPJ1lugpTQcSlMduZLj1EASLO4sC8wt8hmL1aizRlsbjCX+r0PyL+l0xQ64/6Q==", "cpu": [ "arm64" ], @@ -4283,9 +4292,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.10.0.tgz", - "integrity": "sha512-0CZPzqTynUBO+SHEl/qKsFSahp2Jv/P2ZRjFG0gwZY5qIcr1+B/v+o74/GyNMBGz9rft+F2WpU31gz2sJwyF4A==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.10.1.tgz", + "integrity": "sha512-L4BNt1fdQ5ZZhAk5qoDfUnXRabDOXKnXBxMDJ+PWLSxOGBbWE6aJTnu4zbGjJvtot0KM46m2LPAPY8ttknqaZA==", "cpu": [ "x64" ], @@ -4300,9 +4309,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.0.tgz", - "integrity": "sha512-oq+DdMu5uJOFPtRkeiITc4kxmd+QSmK+v+OBzlhdGkSgoH3yRWZP+H2ao0cBXo93ZgCr2LfjiER0CqSKhjGuNA==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.1.tgz", + "integrity": "sha512-Y1u9OqCHgvVp2tYQAJ7hcU9qO5brDMIrA5R31rwWQIAKDkJKtv3IlTHF0hrbWk1wPR0ZdngkQSJZple7G+Grvw==", "cpu": [ "arm" ], @@ -4317,9 +4326,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.0.tgz", - "integrity": "sha512-Y6+PC8knchEViRxiCUj3j8wsGXaIhuvU+WqrFqV834eiItEMEI9+Vh3FovqJMBE3L7d4E4ZQtgImHCXjrHfxbw==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.1.tgz", + "integrity": "sha512-tNQHO/UKdtnqjc7o04iRXng1wTUXPgVd8Y6LI4qIbHVoVPwksZydISjMcilKNLKIwOoUQAkxyJ16SlOAeADzhQ==", "cpu": [ "arm64" ], @@ -4334,9 +4343,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.0.tgz", - "integrity": "sha512-EbrX9A5U4cECCQQfky7945AW9GYnTXtCUXElWTkTYmmyQK87yCyFfY8hmZ9qMFIwxPOH6I3I2JwMhzdi8Qoz7g==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.1.tgz", + "integrity": "sha512-x0L2Pd9weQ6n8dI1z1Isq00VHFvpBClwQJvrt3NHzmR+1wCT/gcYl1tp9P5xHh3ldM8Cn4UjWCw+7PaUgg8FcQ==", "cpu": [ "arm64" ], @@ -4351,9 +4360,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.0.tgz", - "integrity": "sha512-TaxpO6snTjjfLXFYh5EjZ78se69j2gDcqEM8yB9gguPYwkCHi2Ylfmh7iVaNADnDJFtjoAQp0L41bTV/Pfq9Cg==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.1.tgz", + "integrity": "sha512-yyYEwQcObV3AUsC79rSzN9z6kiWxKAVJ6Ntwq2N9YoZqSPYph+4/Am5fM1xEQYf/kb99csj0FgOelomJSobxQA==", "cpu": [ "x64" ], @@ -4368,9 +4377,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.0.tgz", - "integrity": "sha512-IEGvDd6aEEKEyZFZ8oCKuik05G5BS7qwG5hO5PEMzdGeh8JyFZXxsfFXbfeAqjue4UaUUrhnoX+Ze3M2jBVMHw==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.1.tgz", + "integrity": "sha512-tcaS43Ydd7Fk7sW5ROpaf2Kq1zR+sI5K0RM+0qYLYYurvsJruj3GhBCaiN3gkzd8m/8wkqNqtVklWaQYSDsyqA==", "cpu": [ "x64" ], @@ -4385,9 +4394,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.0.tgz", - "integrity": "sha512-UkQ952GSpY+Z6XONj9GSW8xGSkF53jrCsuLj0nrcuw7Dvr1a816U/9WYZmmcYS8tnG2vHylhpm6csQkyS8lpCw==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.1.tgz", + "integrity": "sha512-D3Qo1voA7AkbOzQ2UGuKNHfYGKL6eejN8VWOoQYtGHHQi1p5KK/Q7V1ku55oxXBsj79Ny5FRMqiRJpVGad7bjQ==", "cpu": [ "arm64" ], @@ -4402,9 +4411,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.0.tgz", - "integrity": "sha512-a2QpIZmTiT885u/mUInpeN2W9ClCnqrV2LnMqJR1/Fgx1Afw/hAtiDZPtQ0SqS8yDJ2VR5gfNZo3gpxWMrqdVA==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.1.tgz", + "integrity": "sha512-WalYdFoU3454Og+sDKHM1MrjvxUGwA2oralknXkXL8S0I/8RkWZOB++p3pLaGbTvOO++T+6znFbQdR8KRaa7DA==", "cpu": [ "ia32" ], @@ -4419,9 +4428,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.0.tgz", - "integrity": "sha512-tZcCmMwf483nwsEBfUk5w9e046kMa1iSik4bP9Kwi2FGtOfHuDfIcwW4jek3hdcgF5SaBW1ktnK/lgQLDi5AtA==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.1.tgz", + "integrity": "sha512-JWobfQDbTnoqaIwPKQ3DVSywihVXlQMbDuwik/dDWlj33A8oEHcjPOGs4OqcA3RHv24i+lfCQpM3Mn4FAMfacA==", "cpu": [ "x64" ], @@ -4469,9 +4478,9 @@ "license": "MIT" }, "node_modules/@tanstack/query-core": { - "version": "5.62.7", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.62.7.tgz", - "integrity": "sha512-fgpfmwatsrUal6V+8EC2cxZIQVl9xvL7qYa03gsdsCy985UTUlS4N+/3hCzwR0PclYDqisca2AqR1BVgJGpUDA==", + "version": "5.62.8", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.62.8.tgz", + "integrity": "sha512-4fV31vDsUyvNGrKIOUNPrZztoyL187bThnoQOvAXEVlZbSiuPONpfx53634MKKdvsDir5NyOGm80ShFaoHS/mw==", "license": "MIT", "funding": { "type": "github", @@ -4479,12 +4488,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.62.7", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.62.7.tgz", - "integrity": "sha512-+xCtP4UAFDTlRTYyEjLx0sRtWyr5GIk7TZjZwBu4YaNahi3Rt2oMyRqfpfVrtwsqY2sayP4iXVCwmC+ZqqFmuw==", + "version": "5.62.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.62.8.tgz", + "integrity": "sha512-8TUstKxF/fysHonZsWg/hnlDVgasTdHx6Q+f1/s/oPKJBJbKUWPZEHwLTMOZgrZuroLMiqYKJ9w69Abm8mWP0Q==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.62.7" + "@tanstack/query-core": "5.62.8" }, "funding": { "type": "github", @@ -4921,9 +4930,9 @@ "license": "MIT" }, "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==", + "version": "7946.0.15", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.15.tgz", + "integrity": "sha512-9oSxFzDCT2Rj6DfcHF8G++jxBKS7mBqXl5xrRW+Kbvjry6Uduya2iiwqHPhVXpasAVMBYKkEPGgKhd3+/HZ6xA==", "license": "MIT" }, "node_modules/@types/json-schema": { @@ -5023,12 +5032,11 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.14.tgz", - "integrity": "sha512-NzahNKvjNhVjuPBQ+2G7WlxstQ+47kXZNHlUvFakDViuIEfGY926GqhMueQFZ7woG+sPiQKlF36XfrIUVSUfFg==", + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.2.tgz", + "integrity": "sha512-USU8ZI/xyKJwFTpjSVIrSeHBVAGagkHQKPNbxeWwql/vDmnTIBgx+TJnhFnj1NXgz8XfprU0egV2dROLGpsBEg==", "license": "MIT", "dependencies": { - "@types/prop-types": "*", "csstype": "^3.0.2" } }, @@ -5062,14 +5070,70 @@ "url": "https://opencollective.com/date-fns" } }, - "node_modules/@types/react-dom": { - "version": "18.3.2", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.2.tgz", - "integrity": "sha512-Fqp+rcvem9wEnGr3RY8dYNvSQ8PoLqjZ9HLgaPUOjJJD120uDyOxOjc/39M4Kddp9JQCxpGQbnhVQF0C0ncYVg==", + "node_modules/@types/react-datepicker/node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { - "@types/react": "^18" + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@types/react-datepicker/node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/@types/react-datepicker/node_modules/react-popper": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", + "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "react-fast-compare": "^3.0.1", + "warning": "^4.0.2" + }, + "peerDependencies": { + "@popperjs/core": "^2.0.0", + "react": "^16.8.0 || ^17 || ^18", + "react-dom": "^16.8.0 || ^17 || ^18" + } + }, + "node_modules/@types/react-datepicker/node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/@types/react-dom": { + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.2.tgz", + "integrity": "sha512-c1s+7TKFaDRRxr1TxccIX2u7sfCnc3RxkVyBIUA2lCpyqCF+QoAwQ/CBg7bsMdVwP120HEH143VQezKtef5nCg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" } }, "node_modules/@types/react-linkify": { @@ -5083,11 +5147,11 @@ } }, "node_modules/@types/react-transition-group": { - "version": "4.4.11", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", - "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", "license": "MIT", - "dependencies": { + "peerDependencies": { "@types/react": "*" } }, @@ -5137,17 +5201,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.0.tgz", - "integrity": "sha512-NR2yS7qUqCL7AIxdJUQf2MKKNDVNaig/dEB0GBLU7D+ZdHgK1NoH/3wsgO3OnPVipn51tG3MAwaODEGil70WEw==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.1.tgz", + "integrity": "sha512-Ncvsq5CT3Gvh+uJG0Lwlho6suwDfUXH0HztslDf5I+F2wAFAZMRwYLEorumpKLzmO2suAXZ/td1tBg4NZIi9CQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.18.0", - "@typescript-eslint/type-utils": "8.18.0", - "@typescript-eslint/utils": "8.18.0", - "@typescript-eslint/visitor-keys": "8.18.0", + "@typescript-eslint/scope-manager": "8.18.1", + "@typescript-eslint/type-utils": "8.18.1", + "@typescript-eslint/utils": "8.18.1", + "@typescript-eslint/visitor-keys": "8.18.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -5167,16 +5231,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.0.tgz", - "integrity": "sha512-hgUZ3kTEpVzKaK3uNibExUYm6SKKOmTU2BOxBSvOYwtJEPdVQ70kZJpPjstlnhCHcuc2WGfSbpKlb/69ttyN5Q==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.1.tgz", + "integrity": "sha512-rBnTWHCdbYM2lh7hjyXqxk70wvon3p2FyaniZuey5TrcGBpfhVp0OxOa6gxr9Q9YhZFKyfbEnxc24ZnVbbUkCA==", "dev": true, - "license": "MITClause", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.18.0", - "@typescript-eslint/types": "8.18.0", - "@typescript-eslint/typescript-estree": "8.18.0", - "@typescript-eslint/visitor-keys": "8.18.0", + "@typescript-eslint/scope-manager": "8.18.1", + "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/typescript-estree": "8.18.1", + "@typescript-eslint/visitor-keys": "8.18.1", "debug": "^4.3.4" }, "engines": { @@ -5192,14 +5256,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.0.tgz", - "integrity": "sha512-PNGcHop0jkK2WVYGotk/hxj+UFLhXtGPiGtiaWgVBVP1jhMoMCHlTyJA+hEj4rszoSdLTK3fN4oOatrL0Cp+Xw==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.1.tgz", + "integrity": "sha512-HxfHo2b090M5s2+/9Z3gkBhI6xBH8OJCFjH9MhQ+nnoZqxU3wNxkLT+VWXWSFWc3UF3Z+CfPAyqdCTdoXtDPCQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.18.0", - "@typescript-eslint/visitor-keys": "8.18.0" + "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/visitor-keys": "8.18.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5210,14 +5274,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.0.tgz", - "integrity": "sha512-er224jRepVAVLnMF2Q7MZJCq5CsdH2oqjP4dT7K6ij09Kyd+R21r7UVJrF0buMVdZS5QRhDzpvzAxHxabQadow==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.1.tgz", + "integrity": "sha512-jAhTdK/Qx2NJPNOTxXpMwlOiSymtR2j283TtPqXkKBdH8OAMmhiUfP0kJjc/qSE51Xrq02Gj9NY7MwK+UxVwHQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.18.0", - "@typescript-eslint/utils": "8.18.0", + "@typescript-eslint/typescript-estree": "8.18.1", + "@typescript-eslint/utils": "8.18.1", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -5234,9 +5298,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.0.tgz", - "integrity": "sha512-FNYxgyTCAnFwTrzpBGq+zrnoTO4x0c1CKYY5MuUTzpScqmY5fmsh2o3+57lqdI3NZucBDCzDgdEbIaNfAjAHQA==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.1.tgz", + "integrity": "sha512-7uoAUsCj66qdNQNpH2G8MyTFlgerum8ubf21s3TSM3XmKXuIn+H2Sifh/ES2nPOPiYSRJWAk0fDkW0APBWcpfw==", "dev": true, "license": "MIT", "engines": { @@ -5248,14 +5312,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.0.tgz", - "integrity": "sha512-rqQgFRu6yPkauz+ms3nQpohwejS8bvgbPyIDq13cgEDbkXt4LH4OkDMT0/fN1RUtzG8e8AKJyDBoocuQh8qNeg==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.1.tgz", + "integrity": "sha512-z8U21WI5txzl2XYOW7i9hJhxoKKNG1kcU4RzyNvKrdZDmbjkmLBo8bgeiOJmA06kizLI76/CCBAAGlTlEeUfyg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.18.0", - "@typescript-eslint/visitor-keys": "8.18.0", + "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/visitor-keys": "8.18.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -5314,16 +5378,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.0.tgz", - "integrity": "sha512-p6GLdY383i7h5b0Qrfbix3Vc3+J2k6QWw6UMUeY5JGfm3C5LbZ4QIZzJNoNOfgyRe0uuYKjvVOsO/jD4SJO+xg==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.1.tgz", + "integrity": "sha512-8vikiIj2ebrC4WRdcAdDcmnu9Q/MXXwg+STf40BVfT8exDqBCUPdypvzcUPxEqRGKg9ALagZ0UWcYCtn+4W2iQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.18.0", - "@typescript-eslint/types": "8.18.0", - "@typescript-eslint/typescript-estree": "8.18.0" + "@typescript-eslint/scope-manager": "8.18.1", + "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/typescript-estree": "8.18.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5338,13 +5402,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.0.tgz", - "integrity": "sha512-pCh/qEA8Lb1wVIqNvBke8UaRjJ6wrAWkJO5yyIbs8Yx6TNGYyfNjOo61tLv+WwLvoLPp4BQ8B7AHKijl8NGUfw==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.1.tgz", + "integrity": "sha512-Vj0WLm5/ZsD013YeUKn+K0y8p1M0jPpxOkKdbD1wB0ns53a5piVY02zjf072TblEweAbcYiFiPoSMF3kp+VhhQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.18.0", + "@typescript-eslint/types": "8.18.1", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -5369,9 +5433,9 @@ } }, "node_modules/@uploadcare/file-uploader": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@uploadcare/file-uploader/-/file-uploader-1.11.1.tgz", - "integrity": "sha512-ai0UJw+n2vrOfrWV07MezkY9v2Ck84cAo6gbuDvlNPk0Y8oDChKojm8xIxUlbYqOZwvmmVkXwggTPJlXEVAi4A==", + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@uploadcare/file-uploader/-/file-uploader-1.11.3.tgz", + "integrity": "sha512-5HI5i7vA9kpOEgw2vMUDh7zIha1yZi/pPZ7mATeW56TnPSoL3JnuDZCdV5A7cetPd2GKY5RHLh8vz3yr/oU9FA==", "license": "MIT", "dependencies": { "@symbiotejs/symbiote": "^1.11.7", @@ -5386,14 +5450,6 @@ "integrity": "sha512-q+lUz4MhuDY0pTyLJ6EnTc/+LpQNfVf00qI9ey8SEWnydMb+/v+4O/86Y+IieG5Akbx37Zm0ZolOHLobYdN3Fg==", "license": "MIT" }, - "node_modules/@uploadcare/react-adapter": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@uploadcare/react-adapter/-/react-adapter-0.3.0.tgz", - "integrity": "sha512-XQLPAERn47XC9srFH3HeQpywyInSaJ6JFDKD5XNvaSL5y2KF3MZerwMHsc/brAOLDQox7lLjzPXzrwlWlcMVqw==", - "peerDependencies": { - "@types/react": "17 || 18" - } - }, "node_modules/@uploadcare/react-uploader": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@uploadcare/react-uploader/-/react-uploader-1.3.0.tgz", @@ -5408,6 +5464,14 @@ "next": "13 || 14" } }, + "node_modules/@uploadcare/react-uploader/node_modules/@uploadcare/react-adapter": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@uploadcare/react-adapter/-/react-adapter-0.3.0.tgz", + "integrity": "sha512-XQLPAERn47XC9srFH3HeQpywyInSaJ6JFDKD5XNvaSL5y2KF3MZerwMHsc/brAOLDQox7lLjzPXzrwlWlcMVqw==", + "peerDependencies": { + "@types/react": "17 || 18" + } + }, "node_modules/@uploadcare/upload-client": { "version": "6.14.2", "resolved": "https://registry.npmjs.org/@uploadcare/upload-client/-/upload-client-6.14.2.tgz", @@ -5422,9 +5486,9 @@ } }, "node_modules/@vercel/build-utils": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/@vercel/build-utils/-/build-utils-8.7.0.tgz", - "integrity": "sha512-ofZX+ABiW76u5khIyYyH5xK5KSuiAteqRu5hz2k1a2WHLwF7VpeBg8gdFR+HwbVnNkHtkMA64ya5Dd/lNoABkw==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/@vercel/build-utils/-/build-utils-8.8.0.tgz", + "integrity": "sha512-4jkeJ/Xr0epojgfiyQufB8vC0ubE9SCfA9I2BGcOIKcf65C25juSvuYwaLixnjZXHUTO4Y9W7fdgiLUk55MgbA==", "dev": true, "license": "Apache-2.0" }, @@ -5691,14 +5755,14 @@ } }, "node_modules/@vercel/gatsby-plugin-vercel-builder": { - "version": "2.0.59", - "resolved": "https://registry.npmjs.org/@vercel/gatsby-plugin-vercel-builder/-/gatsby-plugin-vercel-builder-2.0.59.tgz", - "integrity": "sha512-Y3hMNGmi2wyYeOptjjyWOP9Tsx6ME7Xsv1/MhBImWjJrLhqy9L3PFThjJJELP9ghCFGdAB7z7lB8XH4MmczTng==", + "version": "2.0.61", + "resolved": "https://registry.npmjs.org/@vercel/gatsby-plugin-vercel-builder/-/gatsby-plugin-vercel-builder-2.0.61.tgz", + "integrity": "sha512-OchbRapri4fGCefvjOG9m0yP2JICvCwrrQ+jS3lumcvr6NimmdDu3yxnzFYlSZ06D8cqD3hXx7a+hJKY5zdk3Q==", "dev": true, "dependencies": { "@sinclair/typebox": "0.25.24", - "@vercel/build-utils": "8.7.0", - "@vercel/routing-utils": "3.1.0", + "@vercel/build-utils": "8.8.0", + "@vercel/routing-utils": "5.0.0", "esbuild": "0.14.47", "etag": "1.8.1", "fs-extra": "11.1.0" @@ -5785,9 +5849,9 @@ } }, "node_modules/@vercel/node": { - "version": "3.2.29", - "resolved": "https://registry.npmjs.org/@vercel/node/-/node-3.2.29.tgz", - "integrity": "sha512-WRVYidBqtRyYUw36v/WyUB2v97PsiV2+LepUiOPWcW9UpszQGGT2DAzsXOYqWveXMJKFhx0aETR6Nn6i+Yps1Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@vercel/node/-/node-5.0.0.tgz", + "integrity": "sha512-l917aGyDkaOhqfDrYSqy9sjd+Pv6K8mCsVyxzGv4kwmbhERpi8rS6aBmfIf4fDevEPYiOasftPHASbdnyHOe/g==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5795,7 +5859,7 @@ "@edge-runtime/primitives": "4.1.0", "@edge-runtime/vm": "3.2.0", "@types/node": "16.18.11", - "@vercel/build-utils": "8.7.0", + "@vercel/build-utils": "8.8.0", "@vercel/error-utils": "2.0.3", "@vercel/nft": "0.27.3", "@vercel/static-config": "3.0.0", @@ -5873,23 +5937,23 @@ "license": "Apache-2.0" }, "node_modules/@vercel/redwood": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@vercel/redwood/-/redwood-2.1.8.tgz", - "integrity": "sha512-qBUBqIDxPEYnxRh3tsvTaPMtBkyK/D2tt9tBugNPe0OeYnMCMXVj9SJYbxiDI2GzAEFUZn4Poh63CZtXMDb9Tg==", + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@vercel/redwood/-/redwood-2.1.10.tgz", + "integrity": "sha512-JscXZGNuZY1IHq5CbGBHt7BvHEHh35ZIgorJ5RAEjvuqaox/EE4bA0oyI8y/5aWoZfMvJifS+UATKwI8HrP97w==", "dev": true, "license": "Apache-2.0", "dependencies": { "@vercel/nft": "0.27.3", - "@vercel/routing-utils": "3.1.0", + "@vercel/routing-utils": "5.0.0", "@vercel/static-config": "3.0.0", "semver": "6.3.1", "ts-morph": "12.0.0" } }, "node_modules/@vercel/remix-builder": { - "version": "2.2.14", - "resolved": "https://registry.npmjs.org/@vercel/remix-builder/-/remix-builder-2.2.14.tgz", - "integrity": "sha512-w81xbhZh5YZtWBi6E7o9Og9GkT86DZYQ0FBZvR9pAJCG4ejK18SLLyXD2MORLosTFpecLL0VZ5vdPh9oD9hJug==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@vercel/remix-builder/-/remix-builder-4.0.0.tgz", + "integrity": "sha512-82O08BggyQgRR86jbkx6pTWaTVhlMA9vU0nvql8af76r3k0iBrkoe6tJiEnwut2YaePUNWr2Kr+mdy6OrJHmVw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5900,9 +5964,9 @@ } }, "node_modules/@vercel/routing-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@vercel/routing-utils/-/routing-utils-3.1.0.tgz", - "integrity": "sha512-Ci5xTjVTJY/JLZXpCXpLehMft97i9fH34nu9PGav6DtwkVUF6TOPX86U0W0niQjMZ5n6/ZP0BwcJK2LOozKaGw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@vercel/routing-utils/-/routing-utils-5.0.0.tgz", + "integrity": "sha512-llvozDbkGDSelbgigAt9IwCQS8boP4rNHfy3rpJf0DqSn6UDlkFX270NwIQruyXN9KHktHC9qOof6Ik2+bT88A==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -5953,14 +6017,14 @@ "license": "Apache-2.0" }, "node_modules/@vercel/static-build": { - "version": "2.5.37", - "resolved": "https://registry.npmjs.org/@vercel/static-build/-/static-build-2.5.37.tgz", - "integrity": "sha512-2ARBtE3ct7h9V34T+so/U3+ahXdG/0bk9uN96YV4L128nY4RR/T1F1uuY33Tiiz2PHAoq43hhMRZUC06b+GAhw==", + "version": "2.5.39", + "resolved": "https://registry.npmjs.org/@vercel/static-build/-/static-build-2.5.39.tgz", + "integrity": "sha512-gnurv1NWzyE5STFF0BlgWXzIMODv9HeVezRwiEyhndjeugwkzp3xyoRXXR178LZ87lZ7gcgOp6HWEpNmaHDSyw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@vercel/gatsby-plugin-vercel-analytics": "1.0.11", - "@vercel/gatsby-plugin-vercel-builder": "2.0.59", + "@vercel/gatsby-plugin-vercel-builder": "2.0.61", "@vercel/static-config": "3.0.0", "ts-morph": "12.0.0" } @@ -6433,16 +6497,16 @@ } }, "node_modules/array.prototype.flat": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", - "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -6452,16 +6516,16 @@ } }, "node_modules/array.prototype.flatmap": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", - "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -6471,20 +6535,19 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" }, "engines": { "node": ">= 0.4" @@ -6787,9 +6850,9 @@ "license": "MIT" }, "node_modules/browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", + "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", "dev": true, "funding": [ { @@ -6807,9 +6870,9 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { @@ -6941,9 +7004,9 @@ } }, "node_modules/call-bind-apply-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.0.tgz", - "integrity": "sha512-CCKAP2tkPau7D3GE8+V8R6sQubA9R5foIzGp+85EXCVSCivuxBNAWqcpn72PKYiIcqoViv/kcUDpaEIMBVi1lQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", "dev": true, "license": "MIT", "dependencies": { @@ -6954,6 +7017,23 @@ "node": ">= 0.4" } }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -6986,9 +7066,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001687", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001687.tgz", - "integrity": "sha512-0S/FDhf4ZiqrTUiQ39dKeUjYRjkv7lOZU1Dgif2rIqrTzX/1wV2hfKu9TOm1IHkdSijfLswxTFzl/cvir+SLSQ==", + "version": "1.0.30001689", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001689.tgz", + "integrity": "sha512-CmeR2VBycfa+5/jOfnp/NpWPGd06nf1XYiefUvhXFfZE4GkRc9jv+eGPS4nT558WS/8lYCzV8SlANCIPvbWP1g==", "funding": [ { "type": "opencollective", @@ -7591,9 +7671,9 @@ "license": "MIT" }, "node_modules/cypress": { - "version": "13.16.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.16.1.tgz", - "integrity": "sha512-17FtCaz0cx7ssWYKXzGB0Vub8xHwpVPr+iPt2fHhLMDhVAPVrplD+rTQsZUsfb19LVBn5iwkEUFjQ1yVVJXsLA==", + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.17.0.tgz", + "integrity": "sha512-5xWkaPurwkIljojFidhw8lFScyxhtiFHl/i/3zov+1Z5CmY4t9tjIdvSXfu82Y3w7wt0uR9KkucbhkVvJZLQSA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -7867,9 +7947,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -8083,6 +8163,21 @@ "tslib": "^2.0.3" } }, + "node_modules/dunder-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", + "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -8243,9 +8338,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.71", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.71.tgz", - "integrity": "sha512-dB68l59BI75W1BUGVTAEJy45CEVuEGy9qPVVQ8pnHyHMn36PLPPoE1mjLH+lo9rKulO3HC2OhbACI/8tCqJBcA==", + "version": "1.5.73", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.73.tgz", + "integrity": "sha512-8wGNxG9tAG5KhGd3eeA0o6ixhiNdgr0DcHWm85XPCphwZgD1lIEoi6t3VERayWao7SF7AAZTw6oARGJeVjH8Kg==", "dev": true, "license": "ISC" }, @@ -8308,58 +8403,60 @@ } }, "node_modules/es-abstract": { - "version": "1.23.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.5.tgz", - "integrity": "sha512-vlmniQ0WNPwXqA0BnmwV3Ng7HxiGlh6r5U6JcTMNx8OilcAGqVJBHJcPjqOMaczU9fRuRK5Px2BdVyPRnKMMVQ==", + "version": "1.23.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.6.tgz", + "integrity": "sha512-Ifco6n3yj2tMZDWNLyloZrytt9lqqlwvS83P3HtaETR0NUOYnIULGGHpktqYGObGy+8wc1okO25p8TjemhImvA==", "dev": true, "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", + "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "data-view-buffer": "^1.0.1", "data-view-byte-length": "^1.0.1", "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.7", + "get-intrinsic": "^1.2.6", "get-symbol-description": "^1.0.2", "globalthis": "^1.0.4", - "gopd": "^1.0.1", + "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "internal-slot": "^1.0.7", + "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.4", "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", + "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", + "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", + "is-string": "^1.1.1", "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.0.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.5", "regexp.prototype.flags": "^1.5.3", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", + "safe-array-concat": "^1.1.3", + "safe-regex-test": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.2", "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", + "typed-array-byte-offset": "^1.0.3", + "typed-array-length": "^1.0.7", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" + "which-typed-array": "^1.1.16" }, "engines": { "node": ">= 0.4" @@ -8369,14 +8466,11 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -8860,9 +8954,9 @@ } }, "node_modules/eslint": { - "version": "9.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz", - "integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", + "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", "dev": true, "license": "MIT", "dependencies": { @@ -8871,7 +8965,7 @@ "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.9.0", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.16.0", + "@eslint/js": "9.17.0", "@eslint/plugin-kit": "^0.2.3", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -8880,7 +8974,7 @@ "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.5", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", @@ -9600,13 +9694,13 @@ } }, "node_modules/framer-motion": { - "version": "11.14.0", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.14.0.tgz", - "integrity": "sha512-/cPWeZ4GXAJwNj3Z2cp+WtbVDlXyhBWmoIlorgn6FieOcEmoritPGvg+dqO6GaUlXHYgW6YLpsAPzdFfQqWQ5Q==", + "version": "11.15.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.15.0.tgz", + "integrity": "sha512-MLk8IvZntxOMg7lDBLw2qgTHHv664bYoYmnFTmE0Gm/FW67aOJk0WM3ctMcG+Xhcv+vh5uyyXwxvxhSeJzSe+w==", "license": "MIT", "dependencies": { - "motion-dom": "^11.14.0", - "motion-utils": "^11.13.0", + "motion-dom": "^11.14.3", + "motion-utils": "^11.14.3", "tslib": "^2.4.0" }, "peerDependencies": { @@ -9765,16 +9859,17 @@ } }, "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.7.tgz", + "integrity": "sha512-2g4x+HqTJKM9zcJqBSpjoRmdcPFtJM60J3xJisTQSXBWka5XqyBN/2tNUgma1mztTXyDuUsEtYe5qcs7xYzYQA==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" }, "engines": { "node": ">= 0.4" @@ -9846,17 +9941,22 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", "dev": true, "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -9994,9 +10094,9 @@ } }, "node_modules/globals": { - "version": "15.13.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.13.0.tgz", - "integrity": "sha512-49TewVEz0UxZjr1WYYsWpPrhyC/B/pA8Bq0fUmet2n+eR7yn0IvNzNaoBwnK6mdkzcN+se7Ez9zUgULTz2QH4g==", + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", + "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", "dev": true, "license": "MIT", "engines": { @@ -10059,9 +10159,9 @@ "license": "MIT" }, "node_modules/graphql": { - "version": "16.9.0", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.9.0.tgz", - "integrity": "sha512-GGTKBX4SD7Wdb8mqeDLni2oaRGYQWjWHGKPQ24ZMnUtKfcsVoiv4uX8+LJr1K6U5VW2Lu1BwJnj7uiori0YtRw==", + "version": "16.10.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.10.0.tgz", + "integrity": "sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==", "license": "MIT", "peer": true, "engines": { @@ -10139,13 +10239,13 @@ } }, "node_modules/has-proto": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.1.0.tgz", - "integrity": "sha512-QLdzI9IIO1Jg7f9GT1gXpPpXArAn6cS31R1eEZqz08Gc+uQ8/XiqHWt17Fiw+2p6oTTIq5GXEpQkAlA88YRl/Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7" + "dunder-proto": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -10403,15 +10503,15 @@ } }, "node_modules/internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" + "hasown": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -10491,13 +10591,13 @@ } }, "node_modules/is-boolean-object": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.0.tgz", - "integrity": "sha512-kR5g0+dXf/+kXnqI+lu0URKYPKgICtHGGNCDSB10AaUFj3o/HkB3u7WfpRBJGFopxxY0oH3ux7ZsDjLtK7xqvw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.1.tgz", + "integrity": "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" }, "engines": { @@ -10521,9 +10621,9 @@ } }, "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz", + "integrity": "sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==", "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -10536,12 +10636,14 @@ } }, "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, "license": "MIT", "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" }, "engines": { @@ -10552,13 +10654,14 @@ } }, "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, "license": "MIT", "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -10709,13 +10812,13 @@ } }, "node_modules/is-number-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.0.tgz", - "integrity": "sha512-KVSZV0Dunv9DTPkhXwcZ3Q+tUc9TsaE1ZwX5J2WMvsSGS6Md8TFPun5uwh0yRdrNerI6vf/tbJxqSx4c1ZI1Lw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" }, "engines": { @@ -10746,14 +10849,14 @@ } }, "node_modules/is-regex": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.0.tgz", - "integrity": "sha512-B6ohK4ZmoftlUe+uvenXSbPJFo6U37BH7oO1B3nQH8f/7h27N56s85MhUtbFJAziz5dcmuR3i8ovUl35zp8pFA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "gopd": "^1.1.0", + "call-bound": "^1.0.2", + "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" }, @@ -10819,13 +10922,13 @@ } }, "node_modules/is-string": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.0.tgz", - "integrity": "sha512-PlfzajuF9vSo5wErv3MJAKD/nqf9ngAs1NFQYm16nUYFO2IzxJ2hcm+IOCg+EEopdykNNUhVq5cz35cAUxU8+g==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" }, "engines": { @@ -10836,15 +10939,15 @@ } }, "node_modules/is-symbol": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.0.tgz", - "integrity": "sha512-qS8KkNNXUZ/I+nX6QT8ZS1/Yx0A444yhzdTKxCzKkNjQ9sHErBxJnJAgh+f5YhusYECEcjo4XcyH87hn6+ks0A==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "has-symbols": "^1.0.3", - "safe-regex-test": "^1.0.3" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -10909,13 +11012,16 @@ } }, "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.0.tgz", + "integrity": "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2" + "call-bound": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -10992,9 +11098,9 @@ } }, "node_modules/jotai": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.10.3.tgz", - "integrity": "sha512-Nnf4IwrLhNfuz2JOQLI0V/AgwcpxvVy8Ec8PidIIDeRi4KCFpwTFIpHAAcU+yCgnw/oASYElq9UY0YdUUegsSA==", + "version": "2.10.4", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.10.4.tgz", + "integrity": "sha512-/T4ofyMSkAybEs2OvR8S4HACa+/ASUEPLz86SUjFXJqU9RdJKLvZDJrag398suvHC5CR0+Cs4P5m/gtVcryzlw==", "license": "MIT", "engines": { "node": ">=12.20.0" @@ -11045,9 +11151,9 @@ "license": "MIT" }, "node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -11770,6 +11876,16 @@ "markdown-it": "bin/markdown-it.mjs" } }, + "node_modules/math-intrinsics": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", + "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", @@ -12030,14 +12146,16 @@ } }, "node_modules/motion-dom": { - "version": "11.14.0", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.14.0.tgz", - "integrity": "sha512-K7utJZxeMfUJNp/7VNtyasmj4AsqmX6dxT01ox2M9kndcww9soP4gFpf4n0wz7m249+c2ZRfWPnWdInzaxv03w==" + "version": "11.14.3", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.14.3.tgz", + "integrity": "sha512-lW+D2wBy5vxLJi6aCP0xyxTxlTfiu+b+zcpVbGVFUxotwThqhdpPRSmX8xztAgtZMPMeU0WGVn/k1w4I+TbPqA==", + "license": "MIT" }, "node_modules/motion-utils": { - "version": "11.13.0", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.13.0.tgz", - "integrity": "sha512-lq6TzXkH5c/ysJQBxgLXgM01qwBH1b4goTPh57VvZWJbVJZF/0SB31UWEn4EIqbVPf3au88n2rvK17SpDTja1A==" + "version": "11.14.3", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.14.3.tgz", + "integrity": "sha512-Xg+8xnqIJTpr0L/cidfTTBFkvRw26ZtGGuIhA94J9PQ2p4mEa06Xx7QVYZH0BP+EpMSaDlu+q0I0mmvwADPsaQ==", + "license": "MIT" }, "node_modules/mri": { "version": "1.2.0", @@ -12201,9 +12319,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "dev": true, "license": "MIT" }, @@ -13028,9 +13146,9 @@ } }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz", + "integrity": "sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -13125,13 +13243,10 @@ } }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } @@ -13183,6 +13298,35 @@ "url": "https://opencollective.com/date-fns" } }, + "node_modules/react-datepicker/node_modules/react-onclickoutside": { + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.1.tgz", + "integrity": "sha512-LdrrxK/Yh9zbBQdFbMTXPp3dTSN9B+9YJQucdDu3JNKRrbdU+H+/TVONJoWtOwy4II8Sqf1y/DTI6w/vGPYW0w==", + "license": "MIT", + "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-datepicker/node_modules/react-popper": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", + "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", + "license": "MIT", + "dependencies": { + "react-fast-compare": "^3.0.1", + "warning": "^4.0.2" + }, + "peerDependencies": { + "@popperjs/core": "^2.0.0", + "react": "^16.8.0 || ^17 || ^18", + "react-dom": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/react-dnd": { "version": "16.0.1", "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz", @@ -13223,16 +13367,15 @@ } }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.25.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.0.0" } }, "node_modules/react-error-boundary": { @@ -13277,9 +13420,9 @@ } }, "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.0.0.tgz", + "integrity": "sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==", "license": "MIT" }, "node_modules/react-leaflet": { @@ -13359,20 +13502,6 @@ "react": "^16.9.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/react-onclickoutside": { - "version": "6.13.1", - "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.1.tgz", - "integrity": "sha512-LdrrxK/Yh9zbBQdFbMTXPp3dTSN9B+9YJQucdDu3JNKRrbdU+H+/TVONJoWtOwy4II8Sqf1y/DTI6w/vGPYW0w==", - "license": "MIT", - "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-pdf": { "version": "9.1.1", "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-9.1.1.tgz", @@ -13402,21 +13531,6 @@ } } }, - "node_modules/react-popper": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", - "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", - "license": "MIT", - "dependencies": { - "react-fast-compare": "^3.0.1", - "warning": "^4.0.2" - }, - "peerDependencies": { - "@popperjs/core": "^2.0.0", - "react": "^16.8.0 || ^17 || ^18", - "react-dom": "^16.8.0 || ^17 || ^18" - } - }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -13491,21 +13605,6 @@ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", "license": "MIT" }, - "node_modules/react-smooth": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.3.tgz", - "integrity": "sha512-PyxIrra8WZWrMRFcCiJsZ+JqFaxEINAt+v/w++wQKQlmO99Eh3+JTLeKApdTsLX2roBdWYXqPsaS8sO4UmdzIg==", - "license": "MIT", - "dependencies": { - "fast-equals": "^5.0.1", - "prop-types": "^15.8.1", - "react-transition-group": "^4.4.5" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/react-split-pane": { "version": "0.1.92", "resolved": "https://registry.npmjs.org/react-split-pane/-/react-split-pane-0.1.92.tgz", @@ -13581,9 +13680,9 @@ } }, "node_modules/react-window": { - "version": "1.8.10", - "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.10.tgz", - "integrity": "sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==", + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.11.tgz", + "integrity": "sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.0.0", @@ -13593,8 +13692,8 @@ "node": ">8.0.0" }, "peerDependencies": { - "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/readable-stream": { @@ -13656,9 +13755,9 @@ } }, "node_modules/recharts": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.14.1.tgz", - "integrity": "sha512-xtWulflkA+/xu4/QClBdtZYN30dbvTHjxjkh5XTMrH/CQ3WGDDPHHa/LLKCbgoqz0z3UaSH2/blV1i6VNMeh1g==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.0.tgz", + "integrity": "sha512-cIvMxDfpAmqAmVgc4yb7pgm/O1tmmkl/CjrvXuW+62/+7jj/iF9Ykm+hb/UJt42TREHMyd3gb+pkgoa2MxgDIw==", "license": "MIT", "dependencies": { "clsx": "^2.0.0", @@ -13674,8 +13773,8 @@ "node": ">=14" }, "peerDependencies": { - "react": "^16.0.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/recharts-scale": { @@ -13687,6 +13786,27 @@ "decimal.js-light": "^2.4.1" } }, + "node_modules/recharts/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/recharts/node_modules/react-smooth": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.3.tgz", + "integrity": "sha512-PyxIrra8WZWrMRFcCiJsZ+JqFaxEINAt+v/w++wQKQlmO99Eh3+JTLeKApdTsLX2roBdWYXqPsaS8sO4UmdzIg==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/redaxios": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/redaxios/-/redaxios-0.5.1.tgz", @@ -13703,19 +13823,20 @@ } }, "node_modules/reflect.getprototypeof": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.7.tgz", - "integrity": "sha512-bMvFGIUKlc/eSfXNX+aZ+EL95/EgZzuwA0OBPTbZZDEJw/0AkentjMuM1oiRfwHrshqk4RzdgiTg5CcDalXN5g==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.8.tgz", + "integrity": "sha512-B5dj6usc5dkk8uFliwjwDHM8To5/QwdKz9JcBZ8Ic4G1f0YmeeJTtE/ZTdgRFPAfxZFiUaPhZ1Jcs4qeagItGQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", + "dunder-proto": "^1.0.0", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "which-builtin-type": "^1.1.4" + "gopd": "^1.2.0", + "which-builtin-type": "^1.2.0" }, "engines": { "node": ">= 0.4" @@ -13817,6 +13938,19 @@ "regjsparser": "bin/parser" } }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/rehackt": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/rehackt/-/rehackt-0.1.0.tgz", @@ -13871,12 +14005,12 @@ } }, "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.9", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.9.tgz", + "integrity": "sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==", "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -13955,9 +14089,9 @@ } }, "node_modules/rollup": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.0.tgz", - "integrity": "sha512-G9GOrmgWHBma4YfCcX8PjH0qhXSdH8B4HDE2o4/jaxj93S4DPCIDoLcXz99eWMji4hB29UFCEd7B2gwGJDR9cQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.28.1.tgz", + "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==", "dev": true, "license": "MIT", "dependencies": { @@ -13971,24 +14105,25 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.28.0", - "@rollup/rollup-android-arm64": "4.28.0", - "@rollup/rollup-darwin-arm64": "4.28.0", - "@rollup/rollup-darwin-x64": "4.28.0", - "@rollup/rollup-freebsd-arm64": "4.28.0", - "@rollup/rollup-freebsd-x64": "4.28.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.28.0", - "@rollup/rollup-linux-arm-musleabihf": "4.28.0", - "@rollup/rollup-linux-arm64-gnu": "4.28.0", - "@rollup/rollup-linux-arm64-musl": "4.28.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.28.0", - "@rollup/rollup-linux-riscv64-gnu": "4.28.0", - "@rollup/rollup-linux-s390x-gnu": "4.28.0", - "@rollup/rollup-linux-x64-gnu": "4.28.0", - "@rollup/rollup-linux-x64-musl": "4.28.0", - "@rollup/rollup-win32-arm64-msvc": "4.28.0", - "@rollup/rollup-win32-ia32-msvc": "4.28.0", - "@rollup/rollup-win32-x64-msvc": "4.28.0", + "@rollup/rollup-android-arm-eabi": "4.28.1", + "@rollup/rollup-android-arm64": "4.28.1", + "@rollup/rollup-darwin-arm64": "4.28.1", + "@rollup/rollup-darwin-x64": "4.28.1", + "@rollup/rollup-freebsd-arm64": "4.28.1", + "@rollup/rollup-freebsd-x64": "4.28.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.28.1", + "@rollup/rollup-linux-arm-musleabihf": "4.28.1", + "@rollup/rollup-linux-arm64-gnu": "4.28.1", + "@rollup/rollup-linux-arm64-musl": "4.28.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.28.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.28.1", + "@rollup/rollup-linux-riscv64-gnu": "4.28.1", + "@rollup/rollup-linux-s390x-gnu": "4.28.1", + "@rollup/rollup-linux-x64-gnu": "4.28.1", + "@rollup/rollup-linux-x64-musl": "4.28.1", + "@rollup/rollup-win32-arm64-msvc": "4.28.1", + "@rollup/rollup-win32-ia32-msvc": "4.28.1", + "@rollup/rollup-win32-x64-msvc": "4.28.1", "fsevents": "~2.3.2" } }, @@ -14027,15 +14162,16 @@ } }, "node_modules/safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, "engines": { @@ -14066,15 +14202,15 @@ "license": "MIT" }, "node_modules/safe-regex-test": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", - "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "is-regex": "^1.1.4" + "is-regex": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -14103,13 +14239,10 @@ } }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "license": "MIT" }, "node_modules/screenfull": { "version": "6.0.2", @@ -14233,16 +14366,73 @@ } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -14574,16 +14764,19 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -14593,16 +14786,20 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -15177,22 +15374,22 @@ } }, "node_modules/tldts": { - "version": "6.1.65", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.65.tgz", - "integrity": "sha512-xU9gLTfAGsADQ2PcWee6Hg8RFAv0DnjMGVJmDnUmI8a9+nYmapMQix4afwrdaCtT+AqP4MaxEzu7cCrYmBPbzQ==", + "version": "6.1.68", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.68.tgz", + "integrity": "sha512-JKF17jROiYkjJPT73hUTEiTp2OBCf+kAlB+1novk8i6Q6dWjHsgEjw9VLiipV4KTJavazXhY1QUXyQFSem2T7w==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^6.1.65" + "tldts-core": "^6.1.68" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "6.1.65", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.65.tgz", - "integrity": "sha512-Uq5t0N0Oj4nQSbU8wFN1YYENvMthvwU13MQrMJRspYCGLSAZjAfoBOJki5IQpnBM/WFskxxC/gIOTwaedmHaSg==", + "version": "6.1.68", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.68.tgz", + "integrity": "sha512-85TdlS/DLW/gVdf2oyyzqp3ocS30WxjaL4la85EArl9cHUR/nizifKAJPziWewSZjDZS71U517/i6ciUeqtB5Q==", "dev": true, "license": "MIT" }, @@ -15594,16 +15791,19 @@ "license": "MIT" }, "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.2", + "call-bound": "^1.0.3", "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -15905,23 +16105,23 @@ "license": "MIT" }, "node_modules/vercel": { - "version": "39.2.0", - "resolved": "https://registry.npmjs.org/vercel/-/vercel-39.2.0.tgz", - "integrity": "sha512-KXpjRaVY+HVMRMXDXS8jgy6/VvMM4fu1Oq/VeqWxRu6svwoFrVEkrbaSkyFrXxBFDk6GktiZS8srmf9jY1JZ8w==", + "version": "39.2.2", + "resolved": "https://registry.npmjs.org/vercel/-/vercel-39.2.2.tgz", + "integrity": "sha512-FTt0r++eORfQ3TLshYFdq5WiC7xVfbKMLE5YsBzex41yeDSCo5a5KXD6nDyMm+IIlu++XsHEVJRnfOA/JjL/mw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@vercel/build-utils": "8.7.0", + "@vercel/build-utils": "8.8.0", "@vercel/fun": "1.1.0", "@vercel/go": "3.2.1", "@vercel/hydrogen": "1.0.9", "@vercel/next": "4.4.0", - "@vercel/node": "3.2.29", + "@vercel/node": "5.0.0", "@vercel/python": "4.5.1", - "@vercel/redwood": "2.1.8", - "@vercel/remix-builder": "2.2.14", + "@vercel/redwood": "2.1.10", + "@vercel/remix-builder": "4.0.0", "@vercel/ruby": "2.1.0", - "@vercel/static-build": "2.5.37", + "@vercel/static-build": "2.5.39", "chokidar": "4.0.0" }, "bin": { @@ -15970,9 +16170,9 @@ } }, "node_modules/vite": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.3.tgz", - "integrity": "sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.4.tgz", + "integrity": "sha512-zwlH6ar+6o6b4Wp+ydhtIKLrGM/LoqZzcdVmkGAFun0KHTzIzjh+h0kungEx7KJg/PYnC80I4TII9WkjciSR6Q==", "dev": true, "license": "MIT", "dependencies": { @@ -16101,9 +16301,9 @@ } }, "node_modules/vite-plugin-svgr/node_modules/@rollup/pluginutils": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz", - "integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", "dev": true, "license": "MIT", "dependencies": { @@ -16271,17 +16471,17 @@ } }, "node_modules/which-boxed-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.0.tgz", - "integrity": "sha512-Ei7Miu/AXe2JJ4iNF5j/UphAgRoma4trE6PtisM09bPygb3egMH3YLW/befsWb1A1AxvNSFidOFTB18XtnIIng==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", "dev": true, "license": "MIT", "dependencies": { "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.0", - "is-number-object": "^1.1.0", - "is-string": "^1.1.0", - "is-symbol": "^1.1.0" + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -16291,25 +16491,25 @@ } }, "node_modules/which-builtin-type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.0.tgz", - "integrity": "sha512-I+qLGQ/vucCby4tf5HsLmGueEla4ZhwTBSqaooS+Y0BuxN4Cp+okmGuV+8mXZ84KDI9BA+oklo+RzKg0ONdSUA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", - "is-date-object": "^1.0.5", + "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", - "is-regex": "^1.1.4", + "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", - "which-boxed-primitive": "^1.0.2", + "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", - "which-typed-array": "^1.1.15" + "which-typed-array": "^1.1.16" }, "engines": { "node": ">= 0.4" diff --git a/package.json b/package.json index f81ab53373..8a95321bcb 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "test-report": "playwright show-report" }, "dependencies": { - "@apollo/client": "3.12.2", + "@apollo/client": "3.12.3", "@cyntler/react-doc-viewer": "1.17.0", "@dnd-kit/core": "6.3.1", "@dnd-kit/sortable": "10.0.0", @@ -27,10 +27,10 @@ "@fontsource/roboto": "5.1.0", "@fontsource/roboto-mono": "5.1.0", "@json2csv/plainjs": "7.0.6", - "@mui/lab": "6.0.0-beta.19", - "@mui/material": "6.1.10", + "@mui/lab": "6.0.0-beta.20", + "@mui/material": "6.2.1", "@react-leaflet/core": "2.1.0", - "@tanstack/react-query": "5.62.7", + "@tanstack/react-query": "5.62.8", "@turf/ellipse": "7.1.0", "@turf/helpers": "7.1.0", "@turf/nearest-point": "7.1.0", @@ -43,10 +43,10 @@ "dexie": "4.0.10", "exceljs": "4.4.0", "file-saver": "2.0.5", - "framer-motion": "11.14.0", + "framer-motion": "11.15.0", "get-urls": "12.1.0", "is-uuid": "1.0.2", - "jotai": "2.10.3", + "jotai": "2.10.4", "js-file-download": "0.4.12", "jwt-decode": "4.0.0", "leaflet": "1.9.4", @@ -67,12 +67,12 @@ "proj4": "2.15.0", "proj4leaflet": "1.0.2", "query-string": "9.1.1", - "react": "18.3.1", + "react": "19.0.0", "react-copy-to-clipboard": "5.1.0", "react-datepicker": "4.25.0", "react-dnd": "16.0.1", "react-dnd-html5-backend": "16.0.1", - "react-dom": "18.3.1", + "react-dom": "19.0.0", "react-error-boundary": "4.1.2", "react-highlight-words": "0.20.0", "react-icons": "5.4.0", @@ -85,9 +85,10 @@ "react-select": "5.9.0", "react-split-pane": "0.1.92", "react-stylable-diff": "2.0.0", + "react-transition-group": "4.4.5", "react-typography": "0.16.23", - "react-window": "1.8.10", - "recharts": "2.14.1", + "react-window": "1.8.11", + "recharts": "2.15.0", "redaxios": "0.5.1", "screenfull": "6.0.2", "typography": "0.16.24", @@ -98,7 +99,7 @@ "@emotion/babel-plugin": "11.13.5", "@eslint/compat": "1.2.4", "@eslint/eslintrc": "3.2.0", - "@eslint/js": "9.16.0", + "@eslint/js": "9.17.0", "@playwright/test": "1.49.1", "@types/leaflet": "1.9.15", "@types/leaflet-draw": "1.0.11", @@ -106,29 +107,29 @@ "@types/node": "22.10.2", "@types/proj4": "2.5.5", "@types/proj4leaflet": "1.0.10", - "@types/react": "18.3.14", + "@types/react": "19.0.2", "@types/react-datepicker": "4.19.6", - "@types/react-dom": "18.3.2", + "@types/react-dom": "19.0.2", "@types/react-linkify": "1.0.4", - "@typescript-eslint/eslint-plugin": "8.18.0", - "@typescript-eslint/parser": "8.18.0", + "@typescript-eslint/eslint-plugin": "8.18.1", + "@typescript-eslint/parser": "8.18.1", "@vitejs/plugin-react": "4.3.4", "@vitejs/plugin-react-swc": "3.7.2", "copyfiles": "2.4.1", "cross-env": "7.0.3", - "cypress": "13.16.1", + "cypress": "13.17.0", "emotion-swc-plugin": "0.0.5", - "eslint": "9.16.0", + "eslint": "9.17.0", "eslint-plugin-import": "2.31.0", "eslint-plugin-react-hooks": "5.1.0", "eslint-plugin-react-refresh": "0.4.16", - "globals": "15.13.0", + "globals": "15.14.0", "prettier": "3.4.2", "source-map-explorer": "2.5.3", "tslint-config-prettier": "1.18.0", "typescript": "5.7.2", - "vercel": "39.2.0", - "vite": "6.0.3", + "vercel": "39.2.2", + "vite": "6.0.4", "vite-plugin-pwa": "0.21.1", "vite-plugin-svgr": "4.3.0" }, @@ -137,6 +138,7 @@ "react-leaflet-bing-v2": "5.2.3: reinstall after pull-request accepted: https://github.com/TA-Geoforce/react-leaflet-bing-v2/pull/25", "react-split-pane": "0.1.92 creates dependency conflict and thus forces to npm i --force: https://github.com/tomkp/react-split-pane/pull/836. TODO: migrate off of it", "react-contextmenu": "extracted into modules because: archived, caused conflicting peer dependency (old react versions)", - "classnames": "needed by react-contextmenu" + "classnames": "needed by react-contextmenu", + "react-leaflet": "Wait updating from 4.2.1 to 5.0.0 for react-leaflet-marker to update to react v19" } } diff --git a/src/App.jsx b/src/App.jsx index 58d9ea2069..3f53112b91 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -28,8 +28,8 @@ import { buildClient } from './client.js' import { store as jotaiStore } from './JotaiStore/index.js' -import { Provider as MobxProvider } from './storeContext.js' -import { Provider as IdbProvider } from './idbContext.js' +import { MobxContext } from './mobxContext.js' +import { IdbContext } from './idbContext.js' import { UploaderContext } from './UploaderContext.js' const Notifier = lazy(async () => ({ @@ -88,8 +88,8 @@ export const App = () => { return ( - - + + @@ -105,7 +105,7 @@ export const App = () => { autoHideDuration={10000} action={(key) => } > - + }> @@ -122,14 +122,14 @@ export const App = () => { idb={idb} /> - + - - + + ) } diff --git a/src/JotaiStore/index.js b/src/JotaiStore/index.js index 2f2bff1a26..649d770954 100644 --- a/src/JotaiStore/index.js +++ b/src/JotaiStore/index.js @@ -1,5 +1,19 @@ -import { createStore } from 'jotai' +import { createStore, atom } from 'jotai' import { atomWithStorage } from 'jotai/utils' +import { constants } from '../modules/constants.js' + +function atomWithToggleAndStorage(key, initialValue, storage) { + const anAtom = atomWithStorage(key, initialValue, storage) + const derivedAtom = atom( + (get) => get(anAtom), + (get, set, nextValue) => { + const update = nextValue ?? !get(anAtom) + set(anAtom, update) + }, + ) + + return derivedAtom +} export const store = createStore() @@ -11,7 +25,225 @@ export const newTpopFromBeobBeobIdAtom = atomWithStorage( 'newTpopFromBeobBeobId', null, ) -export const listLabelFilterIsIconAtom = atomWithStorage( - 'listLabelFilterIsIcon', - true, +export const enforceDesktopNavigationAtom = atomWithStorage( + 'enforceDesktopNavigation', + false, +) +export const writeEnforceDesktopNavigationAtom = atom( + (get) => get(enforceDesktopNavigationAtom), + (get, set, enforce) => { + if (enforce) { + set(enforceDesktopNavigationAtom, true) + set(enforceMobileNavigationAtom, false) + set(isDesktopViewAtom, true) + return + } + set(enforceDesktopNavigationAtom, false) + const isNowDesktopView = window.innerWidth >= constants.mobileViewMaxWidth + set(isDesktopViewAtom, isNowDesktopView) + return + }, +) +export const enforceMobileNavigationAtom = atomWithStorage( + 'enforceMobileNavigation', + false, +) +export const writeEnforceMobileNavigationAtom = atom( + (get) => get(enforceMobileNavigationAtom), + (get, set, enforce) => { + if (enforce) { + set(enforceMobileNavigationAtom, true) + set(enforceDesktopNavigationAtom, false) + set(isDesktopViewAtom, false) + return + } + set(enforceMobileNavigationAtom, false) + const isNowDesktopView = window.innerWidth >= constants.mobileViewMaxWidth + set(isDesktopViewAtom, isNowDesktopView) + return + }, +) +export const isDesktopViewAtom = atomWithStorage('isDesktopView', false) +export const setDesktopViewAtom = atom( + (get) => get(isDesktopViewAtom), + (get, set, width) => { + const isDesktopView = get(isDesktopViewAtom) + const mobileEnforced = get(enforceMobileNavigationAtom) + const desktopEnforced = get(enforceDesktopNavigationAtom) + if (mobileEnforced) { + if (isDesktopView) set(isDesktopViewAtom, false) + return + } + if (desktopEnforced) { + if (!isDesktopView) set(isDesktopViewAtom, true) + return + } + const isNowDesktopView = width >= constants.mobileViewMaxWidth + if (isNowDesktopView === isDesktopView) return + set(isDesktopViewAtom, isNowDesktopView) + }, +) + +export const isMobileViewAtom = atom( + (get) => !get(isDesktopViewAtom) || get(enforceMobileNavigationAtom), +) +export const hideBookmarksAtom = atom((get) => { + const isDesktopView = get(isDesktopViewAtom) + const enforceMobileNavigation = get(enforceMobileNavigationAtom) + const hideBookmarks = isDesktopView && !enforceMobileNavigation + return hideBookmarks +}) +export const showBookmarksMenuAtom = atomWithStorage('showBookmarksMenu', false) +export const alwaysShowTreeAtom = atomWithStorage('alwaysShowTree', false) +export const hideTreeAtom = atom((get) => { + const alwaysShowTree = get(alwaysShowTreeAtom) + const isMobileView = get(isMobileViewAtom) + const hideTree = !alwaysShowTree && isMobileView + return hideTree +}) +export const adresseNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'adresseNavListFilterIsVisible', + false, +) +export const apNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'apNavListFilterIsVisible', + false, ) +export const apartNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'apartNavListFilterIsVisible', + false, +) +export const apberNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'apberNavListFilterIsVisible', + false, +) +export const apberuebersichtNavListFilterIsVisibleAtom = + atomWithToggleAndStorage('apberuebersichtNavListFilterIsVisible', false) +export const aperfkritNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'aprefkritNavListFilterIsVisible', + false, +) +export const apzielNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'apzielNavListFilterIsVisible', + false, +) +export const apzielberNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'apzielberNavListFilterIsVisible', + false, +) +export const assozartNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'assozartNavListFilterIsVisible', + false, +) +export const beobNichtBeurteiltNavListFilterIsVisibleAtom = + atomWithToggleAndStorage('beobNichtBeurteiltNavListFilterIsVisible', false) +export const beobNichtZuzuordnenNavListFilterIsVisibleAtom = + atomWithToggleAndStorage('beobNichtZuzuordnenNavListFilterIsVisible', false) +export const beobZugeordnetNavListFilterIsVisibleAtom = + atomWithToggleAndStorage('beobZugeordnetNavListFilterIsVisible', false) +export const ekAbrechnungstypWerteNavListFilterIsVisibleAtom = + atomWithToggleAndStorage('ekAbrechnungstypWerteNavListFilterIsVisible', false) +export const ekfrequenzNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'ekfrequenzNavListFilterIsVisible', + false, +) +export const ekzaehleinheitNavListFilterIsVisibleAtom = + atomWithToggleAndStorage('ekzaehleinheitNavListFilterIsVisible', false) +export const erfkritNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'erfkritNavListFilterIsVisible', + false, +) +export const popNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'popNavListFilterIsVisible', + false, +) +export const popberNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'popberNavListFilterIsVisible', + false, +) +export const popmassnberNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'popmassnberNavListFilterIsVisible', + false, +) +export const tpopNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'tpopNavListFilterIsVisible', + false, +) +export const tpopApberrelevantGrundWerteNavListFilterIsVisibleAtom = + atomWithToggleAndStorage( + 'tpopApberrelevantGrundWerteNavListFilterIsVisible', + false, + ) +export const tpopberNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'tpopberNavListFilterIsVisible', + false, +) +export const tpopkontrNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'tpopkontrNavListFilterIsVisible', + false, +) +export const tpopkontrzaehlNavListFilterIsVisibleAtom = + atomWithToggleAndStorage('tpopkontrzaehlNavListFilterIsVisible', false) +export const tpopkontrzaehlEinheitWerteNavListFilterIsVisibleAtom = + atomWithToggleAndStorage( + 'tpopkontrzaehlEinheitWerteNavListFilterIsVisible', + false, + ) +export const tpopmassnNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'tpopmassnNavListFilterIsVisible', + false, +) +export const tpopmassnberNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'tpopmassnberNavListFilterIsVisible', + false, +) +export const userNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'userNavListFilterIsVisible', + false, +) +export const zielNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'zielNavListFilterIsVisible', + false, +) +export const zielberNavListFilterIsVisibleAtom = atomWithToggleAndStorage( + 'zielberNavListFilterIsVisible', + false, +) + +export const navListFilterAtoms = { + adresse: adresseNavListFilterIsVisibleAtom, + ap: apNavListFilterIsVisibleAtom, + apart: apartNavListFilterIsVisibleAtom, + apber: apberNavListFilterIsVisibleAtom, + apberuebersicht: apberuebersichtNavListFilterIsVisibleAtom, + aperfkrit: aperfkritNavListFilterIsVisibleAtom, + apziel: apzielNavListFilterIsVisibleAtom, + apzielber: apzielberNavListFilterIsVisibleAtom, + assozart: assozartNavListFilterIsVisibleAtom, + beobNichtBeurteilt: beobNichtBeurteiltNavListFilterIsVisibleAtom, + beobNichtZuzuordnen: beobNichtZuzuordnenNavListFilterIsVisibleAtom, + beobZugeordnet: beobZugeordnetNavListFilterIsVisibleAtom, + ekAbrechnungstypWerte: ekAbrechnungstypWerteNavListFilterIsVisibleAtom, + ekfrequenz: ekfrequenzNavListFilterIsVisibleAtom, + ekzaehleinheit: ekzaehleinheitNavListFilterIsVisibleAtom, + erfkrit: erfkritNavListFilterIsVisibleAtom, + pop: popNavListFilterIsVisibleAtom, + popber: popberNavListFilterIsVisibleAtom, + popmassnber: popmassnberNavListFilterIsVisibleAtom, + tpop: tpopNavListFilterIsVisibleAtom, + tpopApberrelevantGrundWerte: + tpopApberrelevantGrundWerteNavListFilterIsVisibleAtom, + tpopber: tpopberNavListFilterIsVisibleAtom, + tpopkontr: tpopkontrNavListFilterIsVisibleAtom, + tpopkontrzaehl: tpopkontrzaehlNavListFilterIsVisibleAtom, + tpopkontrzaehlEinheitWerte: + tpopkontrzaehlEinheitWerteNavListFilterIsVisibleAtom, + tpopmassn: tpopmassnNavListFilterIsVisibleAtom, + tpopmassnber: tpopmassnberNavListFilterIsVisibleAtom, + user: userNavListFilterIsVisibleAtom, + ziel: zielNavListFilterIsVisibleAtom, + zielber: zielberNavListFilterIsVisibleAtom, + // needed because the hook can't be called conditionally + // and an atom always needs to be returned + undefined: adresseNavListFilterIsVisibleAtom, +} diff --git a/src/components/AppBar/Bar/Projekte/Daten.jsx b/src/components/AppBar/Bar/Projekte/Daten.jsx index ff73d7287a..73c3fa95a3 100644 --- a/src/components/AppBar/Bar/Projekte/Daten.jsx +++ b/src/components/AppBar/Bar/Projekte/Daten.jsx @@ -1,53 +1,54 @@ -import { memo, useCallback, forwardRef } from 'react' +import { memo, useCallback } from 'react' import Button from '@mui/material/Button' import remove from 'lodash/remove' import styled from '@emotion/styled' import { observer } from 'mobx-react-lite' +import { useAtom } from 'jotai' -import { isMobilePhone } from '../../../../modules/isMobilePhone.js' -import { useSearchParamsState } from '../../../../modules/useSearchParamsState.js' import { StyledButton } from './index.jsx' +import { constants } from '../../../../modules/constants.js' +import { useProjekteTabs } from '../../../../modules/useProjekteTabs.js' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' -export const Daten = memo( - forwardRef(({ treeNr = '', hide = false }, ref) => { - const [projekteTabs, setProjekteTabs] = useSearchParamsState( - 'projekteTabs', - isMobilePhone() ? ['tree'] : ['tree', 'daten'], - ) - const isDaten = projekteTabs.includes(`daten${treeNr}`) - const isTree = projekteTabs.includes(`tree${treeNr}`) +const isMobileView = window.innerWidth <= constants.mobileViewMaxWidth - const onClickButton = useCallback(() => { - const copyOfProjekteTabs = [...projekteTabs] - if (isMobilePhone()) { - // show one tab only - setProjekteTabs([`daten${treeNr}`]) +export const Daten = memo(({ treeNr = '', hide = false, ref }) => { + const [projekteTabs, setProjekteTabs] = useProjekteTabs() + const isDaten = projekteTabs.includes(`daten${treeNr}`) + const isTree = projekteTabs.includes(`tree${treeNr}`) + + const [isDesktopView] = useAtom(isDesktopViewAtom) + + const onClickButton = useCallback(() => { + const copyOfProjekteTabs = [...projekteTabs] + if (isDesktopView) { + if (copyOfProjekteTabs.includes(`daten${treeNr}`)) { + remove(copyOfProjekteTabs, (el) => el === `daten${treeNr}`) } else { - if (copyOfProjekteTabs.includes(`daten${treeNr}`)) { - remove(copyOfProjekteTabs, (el) => el === `daten${treeNr}`) - } else { - copyOfProjekteTabs.push(`daten${treeNr}`) - } - setProjekteTabs(copyOfProjekteTabs) + copyOfProjekteTabs.push(`daten${treeNr}`) } - }, [projekteTabs, setProjekteTabs, treeNr]) - - let followed = projekteTabs.includes('filter') - if (treeNr === '2') { - followed = projekteTabs.includes('filter2') + setProjekteTabs(copyOfProjekteTabs) + } else { + // show one tab only + setProjekteTabs([`daten${treeNr}`]) } + }, [projekteTabs, setProjekteTabs, treeNr]) + + let followed = projekteTabs.includes('filter') + if (treeNr === '2') { + followed = projekteTabs.includes('filter2') + } - return ( - - {`Daten${treeNr === '2' ? ' 2' : ''}`} - - ) - }), -) + return ( + + {`Daten${treeNr === '2' ? ' 2' : ''}`} + + ) +}) diff --git a/src/components/AppBar/Bar/Projekte/More/AlwaysShowTree.jsx b/src/components/AppBar/Bar/Projekte/More/AlwaysShowTree.jsx new file mode 100644 index 0000000000..03d6717882 --- /dev/null +++ b/src/components/AppBar/Bar/Projekte/More/AlwaysShowTree.jsx @@ -0,0 +1,45 @@ +import { memo, useCallback } from 'react' +import FormControlLabel from '@mui/material/FormControlLabel' +import Checkbox from '@mui/material/Checkbox' +import { Tooltip } from '@mui/material' +import { useAtom } from 'jotai' +import styled from '@emotion/styled' + +import { alwaysShowTreeAtom } from '../../../../../JotaiStore/index.js' +import { constants } from '../../../../../modules/constants.js' + +const StyledFormControlLabel = styled(FormControlLabel)` + margin-right: 0 !important; +` + +export const AlwaysShowTree = memo(() => { + const [alwaysShowTree, setAlwaysShowTree] = useAtom(alwaysShowTreeAtom) + const toggleAlwaysShowTree = useCallback(() => { + setAlwaysShowTree(!alwaysShowTree) + }, [alwaysShowTree, setAlwaysShowTree]) + + return ( + +
{`Ist normalerweise nur auf grossen Bildschirmen verfügbar (ab ${constants.mobileViewMaxWidth + 1} Pixeln Breite).`}
+
{`Auf Touch-Geräten sind die Kontext-Menüs nicht verfügbar.`}
+ + } + // if window width > 731 left + placement={window.innerWidth > 730 ? 'left' : 'bottom'} + > + + } + label="Navigationsbaum immer anbieten" + /> +
+ ) +}) diff --git a/src/components/AppBar/Bar/Projekte/More/EnforceDesktopNavigation.jsx b/src/components/AppBar/Bar/Projekte/More/EnforceDesktopNavigation.jsx new file mode 100644 index 0000000000..e46cff1893 --- /dev/null +++ b/src/components/AppBar/Bar/Projekte/More/EnforceDesktopNavigation.jsx @@ -0,0 +1,43 @@ +import { memo, useCallback } from 'react' +import FormControlLabel from '@mui/material/FormControlLabel' +import Checkbox from '@mui/material/Checkbox' +import { Tooltip } from '@mui/material' +import { useAtom } from 'jotai' +import styled from '@emotion/styled' + +import { + enforceDesktopNavigationAtom, + writeEnforceDesktopNavigationAtom, +} from '../../../../../JotaiStore/index.js' +import { constants } from '../../../../../modules/constants.js' + +const StyledFormControlLabel = styled(FormControlLabel)` + margin-right: 0 !important; +` + +export const EnforceDesktopNavigation = memo(() => { + const [enforceDesktopNavigation] = useAtom(enforceDesktopNavigationAtom) + const [, setEnforceDesktopNavigation] = useAtom( + writeEnforceDesktopNavigationAtom, + ) + const toggleEnforceDesktopNavigation = useCallback(() => { + setEnforceDesktopNavigation(!enforceDesktopNavigation) + }, [enforceDesktopNavigation, setEnforceDesktopNavigation]) + + return ( + 730 ? 'left' : 'bottom'} + > + + } + label="Desktop-Navigation erzwingen" + /> + + ) +}) diff --git a/src/components/AppBar/Bar/Projekte/More/EnforceMobileNavigation.jsx b/src/components/AppBar/Bar/Projekte/More/EnforceMobileNavigation.jsx new file mode 100644 index 0000000000..47251e9fe7 --- /dev/null +++ b/src/components/AppBar/Bar/Projekte/More/EnforceMobileNavigation.jsx @@ -0,0 +1,43 @@ +import { memo, useCallback } from 'react' +import FormControlLabel from '@mui/material/FormControlLabel' +import Checkbox from '@mui/material/Checkbox' +import { Tooltip } from '@mui/material' +import { useAtom } from 'jotai' +import styled from '@emotion/styled' + +import { + enforceMobileNavigationAtom, + writeEnforceMobileNavigationAtom, +} from '../../../../../JotaiStore/index.js' +import { constants } from '../../../../../modules/constants.js' + +const StyledFormControlLabel = styled(FormControlLabel)` + margin-right: 0 !important; +` + +export const EnforceMobileNavigation = memo(() => { + const [enforceMobileNavigation] = useAtom(enforceMobileNavigationAtom) + const [, setEnforceMobileNavigation] = useAtom( + writeEnforceMobileNavigationAtom, + ) + const toggleEnforceMobileNavigation = useCallback(() => { + setEnforceMobileNavigation(!enforceMobileNavigation) + }, [enforceMobileNavigation, setEnforceMobileNavigation]) + + return ( + 730 ? 'left' : 'bottom'} + > + + } + label="Mobile-Navigation erzwingen" + /> + + ) +}) diff --git a/src/components/AppBar/Bar/Projekte/More/ShowBookmarksMenu.jsx b/src/components/AppBar/Bar/Projekte/More/ShowBookmarksMenu.jsx new file mode 100644 index 0000000000..f030fb52a7 --- /dev/null +++ b/src/components/AppBar/Bar/Projekte/More/ShowBookmarksMenu.jsx @@ -0,0 +1,38 @@ +import { memo, useCallback } from 'react' +import FormControlLabel from '@mui/material/FormControlLabel' +import Checkbox from '@mui/material/Checkbox' +import { Tooltip } from '@mui/material' +import { useAtom } from 'jotai' +import styled from '@emotion/styled' + +import { showBookmarksMenuAtom } from '../../../../../JotaiStore/index.js' + +const StyledFormControlLabel = styled(FormControlLabel)` + margin-right: 0 !important; +` + +export const ShowBookmarksMenu = memo(() => { + const [showBookmarksMenu, setShowBookmarksMenu] = useAtom( + showBookmarksMenuAtom, + ) + const toggleShowBookmarksMenu = useCallback(() => { + setShowBookmarksMenu(!showBookmarksMenu) + }, [showBookmarksMenu, setShowBookmarksMenu]) + + return ( + 730 ? 'left' : 'bottom'} + > + + } + label="In Bookmarks ein Menü anzeigen" + /> + + ) +}) diff --git a/src/components/AppBar/Bar/Projekte/More/index.jsx b/src/components/AppBar/Bar/Projekte/More/index.jsx index 2500915388..c1b77924bd 100644 --- a/src/components/AppBar/Bar/Projekte/More/index.jsx +++ b/src/components/AppBar/Bar/Projekte/More/index.jsx @@ -5,13 +5,25 @@ import Button from '@mui/material/Button' import styled from '@emotion/styled' import { observer } from 'mobx-react-lite' import { useParams } from 'react-router' +import { useAtom } from 'jotai' -import { isMobilePhone } from '../../../../../modules/isMobilePhone.js' import { logout } from '../../../../../modules/logout.js' import { EkfUser } from './EkfUser/index.jsx' -import { StoreContext } from '../../../../../storeContext.js' +import { MobxContext } from '../../../../../mobxContext.js' import { IdbContext } from '../../../../../idbContext.js' -import { useSearchParamsState } from '../../../../../modules/useSearchParamsState.js' +import { useProjekteTabs } from '../../../../../modules/useProjekteTabs.js' +import { ShowBookmarksMenu } from './ShowBookmarksMenu.jsx' +import { EnforceDesktopNavigation } from './EnforceDesktopNavigation.jsx' +import { EnforceMobileNavigation } from './EnforceMobileNavigation.jsx' +import { AlwaysShowTree } from './AlwaysShowTree.jsx' +import { constants } from '../../../../../modules/constants.js' +import { + isMobileViewAtom, + isDesktopViewAtom, + enforceDesktopNavigationAtom, + enforceMobileNavigationAtom, + writeEnforceDesktopNavigationAtom, +} from '../../../../../JotaiStore/index.js' const Container = styled.div` margin-top: auto; @@ -35,22 +47,20 @@ export const More = memo( forwardRef(({ onClickExporte: passedOnClickExporte, role }, ref) => { const { projId } = useParams() - const store = useContext(StoreContext) + const [isMobileView] = useAtom(isMobileViewAtom) + + const store = useContext(MobxContext) const { deletedDatasets, user, setShowDeletions } = store const { idb } = useContext(IdbContext) const [anchorEl, setAnchorEl] = useState(null) const closeMenu = useCallback(() => setAnchorEl(null), []) + /** * need to clone projekteTabs * because otherwise removing elements errors out (because elements are sealed) */ - - const isMobile = isMobilePhone() - const [projekteTabs] = useSearchParamsState( - 'projekteTabs', - isMobile ? ['tree'] : ['tree', 'daten'], - ) + const [projekteTabs] = useProjekteTabs() const exporteIsActive = !!projId const showDeletedDatasets = useCallback(() => { @@ -96,7 +106,7 @@ export const More = memo( open={Boolean(anchorEl)} onClose={closeMenu} > - {isMobile && exporteIsActive && ( + {isMobileView && exporteIsActive && ( )} + + + + {isMobileView && ( + + + + )} + + + + + + Verfügbarkeit der Server von apflora.ch - Version: 1.114.7 vom 12.12.2024 + Version: 1.114.4 vom 26.11.2024 ) diff --git a/src/components/AppBar/Bar/Projekte/index.jsx b/src/components/AppBar/Bar/Projekte/index.jsx index 540447b10e..716ccc9f4c 100644 --- a/src/components/AppBar/Bar/Projekte/index.jsx +++ b/src/components/AppBar/Bar/Projekte/index.jsx @@ -5,14 +5,16 @@ import styled from '@emotion/styled' import { jwtDecode } from 'jwt-decode' import { observer } from 'mobx-react-lite' import { Link, useParams, useLocation } from 'react-router' +import { useAtom } from 'jotai' -import { isMobilePhone } from '../../../../modules/isMobilePhone.js' import { More } from './More/index.jsx' import { Daten } from './Daten.jsx' -import { StoreContext } from '../../../../storeContext.js' -import { useSearchParamsState } from '../../../../modules/useSearchParamsState.js' +import { MobxContext } from '../../../../mobxContext.js' +import { useProjekteTabs } from '../../../../modules/useProjekteTabs.js' import { MenuBar } from '../../../shared/MenuBar/index.jsx' -import { minWidthToShowAllMenus } from '../../index.jsx' +import { constants } from '../../../../modules/constants.js' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { hideTreeAtom } from '../../../../JotaiStore/index.js' // getting widths of elements from refs // BEWARE: ref.current is only set on first render @@ -40,6 +42,9 @@ export const StyledButton = styled(Button)` margin-right: ${(props) => props.followed === 'true' ? '-1px' : 'unset'} !important; text-transform: none !important; + ${(props) => + props.inmenu === 'true' && `border: 1px solid #ab9518 !important;`} + text-wrap: none; // prevent text from breaking into multiple lines flex-shrink: 0; flex-grow: 0; @@ -49,33 +54,50 @@ const DokuButton = styled(Button)` text-transform: none !important; flex-shrink: 0; flex-grow: 0; + text-transform: none !important; + text-wrap: none; + ${(props) => + props.inmenu === 'true' && `border: 1px solid #ab9518 !important;`}; ` export const ProjekteMenus = memo( - observer(({ showAllMenus }) => { + observer(() => { const { projId } = useParams() const { search } = useLocation() - const store = useContext(StoreContext) + const [isDesktopView] = useAtom(isDesktopViewAtom) + const isMobileView = !isDesktopView + + const [hideTree] = useAtom(hideTreeAtom) + + const store = useContext(MobxContext) const { user } = store const { resetTree2Src } = store.tree - const isMobile = isMobilePhone() - const token = user?.token const tokenDecoded = token ? jwtDecode(token) : null const role = tokenDecoded ? tokenDecoded.role : null - const [projekteTabs, setProjekteTabs] = useSearchParamsState( - 'projekteTabs', - isMobilePhone() ? ['tree'] : ['tree', 'daten'], - ) + const [projekteTabs, setProjekteTabs] = useProjekteTabs() const onClickButton = useCallback( (name) => { - if (isMobile) { + if (isMobileView) { // show one tab only - setProjekteTabs([name]) + if (projekteTabs.length === 1) { + setProjekteTabs([name]) + } else { + // if multiple tabs are visible, close the clicked one + // UNLESS the clicked one was not yet visible - then open it and close non tree ones + if (projekteTabs.includes(name)) { + setProjekteTabs([...projekteTabs.filter((el) => el !== name)]) + } else { + setProjekteTabs([ + ...projekteTabs.filter((el) => el === 'tree'), + name, + ]) + } + } } else { const newProjekteTabs = [...projekteTabs] if (newProjekteTabs.includes(name)) { @@ -90,7 +112,7 @@ export const ProjekteMenus = memo( setProjekteTabs(newProjekteTabs) } }, - [isMobile, setProjekteTabs, projekteTabs], + [isMobileView, setProjekteTabs, projekteTabs], ) const onClickTree = useCallback( () => onClickButton('tree'), @@ -133,19 +155,21 @@ export const ProjekteMenus = memo( return ( - - Strukturbaum - + {isDesktopView && ( + + Strukturbaum + + )} )} - {(showAllMenus || tree2IsVisible) && ( + {(isDesktopView || tree2IsVisible) && ( )} - {((showAllMenus && tree2IsVisible) || daten2IsVisible) && ( + {((isDesktopView && tree2IsVisible) || daten2IsVisible) && ( )} - {((showAllMenus && tree2IsVisible) || filter2IsVisible) && ( + {((isDesktopView && tree2IsVisible) || filter2IsVisible) && ( )} - {showAllMenus && !!projId && ( + {isDesktopView && !!projId && ( Dokumentation + {/* in mobile view: move tree to the end of the menus */} + {/* only show if user did not decide to always show */} + {/* do not hide if tree is visible - user can't close it! */} + {isMobileView && (!hideTree || treeIsVisible) && ( + + Strukturbaum + + )} { - const isMobile = isMobilePhone() - const { search, pathname } = useLocation() const showHome = pathname === '/' const showEkPlan = pathname.includes('/EK-Planung') @@ -64,22 +61,20 @@ export const Bar = memo(() => { refreshOptions: { leading: false, trailing: true }, }) - const showAllMenus = !isMobile && width >= minWidthToShowAllMenus - const menuDivRef = useRef(null) const menuDivWidth = menuDivRef.current?.offsetWidth ?? 0 return ( AP Flora @@ -90,7 +85,7 @@ export const Bar = memo(() => { : showEkPlan ? - : } + : } ) diff --git a/src/components/AppBar/EkfBar/Menus.jsx b/src/components/AppBar/EkfBar/Menus.jsx index ea8d344a89..c53b73b665 100644 --- a/src/components/AppBar/EkfBar/Menus.jsx +++ b/src/components/AppBar/EkfBar/Menus.jsx @@ -14,7 +14,7 @@ import { useParams, useLocation } from 'react-router' import { EkfYear } from './EkfYear.jsx' import { User } from './User/index.jsx' -import { StoreContext } from '../../../storeContext.js' +import { MobxContext } from '../../../mobxContext.js' import { dataByUserId as dataByUserIdQuery } from '../../Ekf/dataByUserId.js' import { dataWithDateByUserId as dataWithDateByUserIdQuery } from '../../Ekf/dataWithDateByUserId.js' @@ -54,7 +54,7 @@ export const Menus = memo( const { userId, ekfId, ekfYear } = useParams() const { search } = useLocation() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { user, setIsPrint, setIsEkfSinglePrint } = store const ekfIsActive = !!ekfId diff --git a/src/components/AppBar/EkfBar/User/index.jsx b/src/components/AppBar/EkfBar/User/index.jsx index 4250b2dacf..4a29354e7b 100644 --- a/src/components/AppBar/EkfBar/User/index.jsx +++ b/src/components/AppBar/EkfBar/User/index.jsx @@ -20,7 +20,7 @@ import { Error } from '../../../shared/Error.jsx' import { updateUserById as updateUserByIdGql } from './updateUserById.js' import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { logout } from '../../../../modules/logout.js' import { IdbContext } from '../../../../idbContext.js' @@ -52,7 +52,7 @@ const AbmeldenButton = styled(Button)` ` export const User = ({ username, userOpen, toggleUserOpen }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { idb } = useContext(IdbContext) const { data, error, loading } = useQuery(query, { diff --git a/src/components/AppBar/index.jsx b/src/components/AppBar/index.jsx index 257849e4fe..2f543935b1 100644 --- a/src/components/AppBar/index.jsx +++ b/src/components/AppBar/index.jsx @@ -2,16 +2,17 @@ import { memo, Suspense, useEffect, useContext } from 'react' import styled from '@emotion/styled' import { Outlet, useLocation, useParams, useNavigate } from 'react-router' import { observer } from 'mobx-react-lite' +import { useAtom } from 'jotai' import { Bar } from './Bar/index.jsx' import { EkfBar } from './EkfBar/index.jsx' import { inIframe } from '../../modules/inIframe.js' import { Spinner } from '../shared/Spinner.jsx' -import { StoreContext } from '../../storeContext.js' +import { MobxContext } from '../../mobxContext.js' +import { constants } from '../../modules/constants.js' +import { isMobileViewAtom } from '../../JotaiStore/index.js' const isInIframe = inIframe() -export const minWidthToShowAllMenus = 920 -export const minWidthToShowTitle = 1030 const Container = styled.div` height: 100dvh; @@ -31,11 +32,11 @@ const Appbar = styled.div` flex-direction: row; justify-content: space-between; align-items: center; - padding: 4px 0; + padding: ${(props) => (props.mobile === 'true' ? 0 : 4)}px 0; background-color: #2e7d32; height: 38px; - @media (max-width: ${minWidthToShowTitle - 1}px) { + @media (max-width: ${constants.minWidthToShowTitle - 1}px) { justify-content: flex-end; } @@ -50,14 +51,18 @@ export const Component = memo( const { userId } = useParams() const { pathname, search } = useLocation() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const activeNodeArray = store.tree.activeNodeArray + const [isMobileView] = useAtom(isMobileViewAtom) + useEffect(() => { if (isInIframe) return // if app was opened on top level, navigate to last active node - if (pathname === '/') { + // but only if activeNodeArray is not empty + // otherwise first time users are navigated to the login + if (pathname === '/' && activeNodeArray.length > 0) { navigate('/Daten/' + activeNodeArray.join('/') + search) } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -70,7 +75,7 @@ export const Component = memo( return ( - + {showEkf ? : } diff --git a/src/components/Bookmarks/Bookmark/Label.jsx b/src/components/Bookmarks/Bookmark/Label.jsx new file mode 100644 index 0000000000..585e431567 --- /dev/null +++ b/src/components/Bookmarks/Bookmark/Label.jsx @@ -0,0 +1,101 @@ +import { memo, useCallback, useMemo, useContext } from 'react' +import { Link, useLocation, useNavigate } from 'react-router' +import Tooltip from '@mui/material/Tooltip' +import styled from '@emotion/styled' +import { observer } from 'mobx-react-lite' + +import { toggleNodeSymbol } from '../../Projekte/TreeContainer/Tree/toggleNodeSymbol.js' +import { MobxContext } from '../../../mobxContext.js' + +const StyledLink = styled(Link)` + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-decoration: none; + align-content: center; + transition: opacity 700ms ease-in-out; + user-select: none; + &:hover { + text-decoration: underline; + text-decoration-color: rgba(55, 118, 28, 0.5); + } +` +const StyledText = styled.div` + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + align-content: center; + transition: opacity 700ms ease-in-out; + user-select: none; +` + +export const Label = memo( + observer(({ navData, outerContainerRef, labelStyle, ref }) => { + const { pathname, search } = useLocation() + const navigate = useNavigate() + const store = useContext(MobxContext) + + // issue: relative paths are not working!!!??? + // also: need to decode pathname (Zähleinheiten...) + const pathnameDecoded = decodeURIComponent(pathname) + const pathnameWithoutLastSlash = pathnameDecoded.replace(/\/$/, '') + const linksToSomewhereElse = !pathnameWithoutLastSlash.endsWith(navData.url) + + const onClick = useCallback(() => { + // 1. ensure the clicked element is visible + const element = outerContainerRef.current + if (!element) return + // the timeout needs to be rather long to wait for the transition to finish + setTimeout(() => { + element.scrollIntoView({ + inline: 'start', + }) + }, 1000) + // 2. sync tree openNodes + toggleNodeSymbol({ + node: { + url: navData.url + .split('/') + .filter((e) => !!e) + .slice(1), + }, + store, + search, + navigate, + }) + }, []) + + const label = useMemo( + () => + linksToSomewhereElse ? + + {navData.labelShort ?? navData.label} + + : + {navData.labelShort ?? navData.label} + , + [ + linksToSomewhereElse, + navData.label, + navData.labelShort, + navData.url, + search, + labelStyle, + ], + ) + + // tooltip can mess with touch, so hide it on touch devices + if (!matchMedia('(pointer: coarse)').matches) { + return {label} + } + return label + }), +) diff --git a/src/components/Bookmarks/Bookmark/Menu/Item.jsx b/src/components/Bookmarks/Bookmark/Menu/Item.jsx new file mode 100644 index 0000000000..7ac98a935c --- /dev/null +++ b/src/components/Bookmarks/Bookmark/Menu/Item.jsx @@ -0,0 +1,62 @@ +import { memo, useCallback, useContext, useMemo } from 'react' +import { observer } from 'mobx-react-lite' +import MenuItem from '@mui/material/MenuItem' +import { useLocation, useNavigate } from 'react-router' +import isEqual from 'lodash/isEqual' + +import { menuIsInActiveNodePath } from './menuIsInActiveNodePath.js' +import { MobxContext } from '../../../../mobxContext.js' + +export const Item = memo( + observer(({ menu, baseUrl, onClose }) => { + const { pathname, search } = useLocation() + const navigate = useNavigate() + + const store = useContext(MobxContext) + const activeNodeArray = store.tree.activeNodeArray + + // issue: relative paths are not working!!!??? + const pathnameWithoutLastSlash = pathname.replace(/\/$/, '') + + const selected = useMemo( + () => + menuIsInActiveNodePath({ + menuUrl: `${baseUrl}/${menu.id}` + .split('/') + .filter((p) => !!p) + .filter((p) => p !== 'Daten'), + activeNodeArray, + }), + [activeNodeArray, baseUrl, menu.id], + ) + const onClick = useCallback(() => { + // 1. navigate + const pathname = `${baseUrl ?? pathnameWithoutLastSlash}/${menu.id}` + navigate({ pathname, search }) + // 2. sync tree openNodes + const url = pathname + .split('/') + .filter((e) => !!e) + .slice(1) + const newOpenNodes = store.tree.openNodes.filter( + (n) => !isEqual(n.slice(0, url.length), url), + ) + store.tree.setOpenNodes(newOpenNodes) + // 3. close menu + onClose() + }, [navigate, onClose, baseUrl, pathnameWithoutLastSlash, menu.id, search]) + + return ( + + {!!menu.labelLeftElements?.length && + menu.labelLeftElements.map((El, index) => )} + {menu.label} + {!!menu.labelRightElements?.length && + menu.labelRightElements.map((El, index) => )} + + ) + }), +) diff --git a/src/components/Bookmarks/Bookmark/Menu/Title/FilterInput.jsx b/src/components/Bookmarks/Bookmark/Menu/Title/FilterInput.jsx new file mode 100644 index 0000000000..6af935a3c6 --- /dev/null +++ b/src/components/Bookmarks/Bookmark/Menu/Title/FilterInput.jsx @@ -0,0 +1,119 @@ +import { + memo, + useContext, + useState, + useCallback, + useEffect, + useRef, +} from 'react' +import { observer } from 'mobx-react-lite' +import { useDebouncedCallback } from 'use-debounce' +import IconButton from '@mui/material/IconButton' +import Tooltip from '@mui/material/Tooltip' +import TextField from '@mui/material/TextField' +import InputAdornment from '@mui/material/InputAdornment' +import { FaTimes } from 'react-icons/fa' +import { MdFilterAlt } from 'react-icons/md' +import styled from '@emotion/styled' + +import { MobxContext } from '../../../../../mobxContext.js' + +const height = 40 + +const Container = styled.div` + padding: 4px 16px 4px 16px; + ${(props) => (props.show === 'true' ? '' : 'display: none;')} +` +const StyledTextField = styled(TextField)` + width: ${(props) => (props.width ?? 32) - 32}px; +` + +export const FilterInput = memo( + observer(({ width, filterInputIsVisible, ref: inputRef }) => { + const store = useContext(MobxContext) + const { nodeLabelFilter, activeFilterTable } = store.tree + + const { setKey: setNodeLabelFilterKey, isFiltered: runIsFiltered } = + nodeLabelFilter + const isFiltered = runIsFiltered() + const showFilter = isFiltered + + const filterValue = nodeLabelFilter?.[activeFilterTable] ?? '' + const [value, setValue] = useState(filterValue) + useEffect(() => { + if (filterValue === value) return + setValue(filterValue) + }, [filterValue]) + + const setNodeLabelFilterAfterChange = useCallback( + (val) => { + setNodeLabelFilterKey({ + value: val, + key: activeFilterTable, + }) + }, + [setNodeLabelFilterKey, activeFilterTable], + ) + const setNodeLabelFilterDebounced = useDebouncedCallback( + setNodeLabelFilterAfterChange, + 600, + ) + + const onChange = useCallback( + (e) => { + // remove some values as they can cause exceptions in regular expressions + const val = e.target.value.replaceAll('(', '').replaceAll(')', '') + + setValue(val) + setNodeLabelFilterDebounced(val) + }, + [setNodeLabelFilterDebounced], + ) + + const onClickEmpty = useCallback(() => { + setValue('') + setNodeLabelFilterAfterChange('') + setTimeout(() => inputRef?.current?.focus?.(), 0) + }, [setNodeLabelFilterAfterChange]) + + // if no activeFilterTable, show nothing + if (!activeFilterTable) return null + + return ( + + + + + + + + + : null, + }, + }} + /> + + ) + }), +) diff --git a/src/components/Bookmarks/Bookmark/Menu/Title/index.jsx b/src/components/Bookmarks/Bookmark/Menu/Title/index.jsx new file mode 100644 index 0000000000..1534351bc5 --- /dev/null +++ b/src/components/Bookmarks/Bookmark/Menu/Title/index.jsx @@ -0,0 +1,136 @@ +import { memo, useState, useCallback, useMemo, useEffect } from 'react' +import IconButton from '@mui/material/IconButton' +import Tooltip from '@mui/material/Tooltip' +import Collapse from '@mui/material/Collapse' +import { MdFilterAlt } from 'react-icons/md' +import styled from '@emotion/styled' +import isUuid from 'is-uuid' +import { useResizeDetector } from 'react-resize-detector' +import { FilterInput } from './FilterInput.jsx' +import { ApFilter } from '../../../../Projekte/TreeContainer/ApFilter/index.jsx' + +const Container = styled.div` + position: relative; + display: flex; + flex-direction: column; + z-index: 1; + border-radius: 4px; + min-width: ${(props) => props.minwidth}px; +` +const ContentWrapper = styled.div` + position: fixed; + display: flex; + flex-direction: column; + min-width: ${(props) => props.minwidth}px; + background-color: white; +` +const MenuTitle = styled.div` + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 0.6666667px solid rgba(0, 0, 0, 0.12); + border-radius: 4px; + font-weight: bold; + min-height: 40px; +` +const TitleDiv = styled.div` + padding: 0 16px; + user-select: none; + cursor: default; + white-space: nowrap; + overflow: hidden; + flex-shrink: 0; +` +const Filters = styled.div` + margin-right: 8px; + display: flex; + align-items: center; + flex-grow: 0; + flex-shrink: 1; +` +const StyledTooltip = styled(Tooltip)` + ${(props) => (props.show === 'true' ? '' : 'display: none;')} +` +const ApFilterFitter = styled.div` + margin-top: -14px; + margin-left: 15px; + margin-right: -15px; + width: 58px; +` + +export const Title = memo( + ({ + navData, + width: parentWidth, + filterInputIsVisible, + toggleFilterInput, + setTitleWidth, + ref: filterInputRef, + }) => { + const isUuidList = useMemo( + () => navData.menus.some((menu) => isUuid.anyNonNil(menu.id)), + [navData.menus], + ) + + // if is Aps, need to add ApFilter + const isAps = navData.id === 'Arten' + + const { width: titleWidth, ref } = useResizeDetector({ + handleHeight: false, + refreshMode: 'debounce', + refreshRate: 300, + refreshOptions: { leading: false, trailing: true }, + }) + useEffect(() => { + setTitleWidth( + (titleWidth ?? 40) + + (isUuidList ? + isAps ? 40 + 58 + : 40 + : 0) + + 32 + + 8, + ) + }, [titleWidth, setTitleWidth, isUuidList]) + + // minWidth is the larger of parentWidth and width + const minWidth = Math.max(parentWidth ?? 0, (titleWidth ?? 40) + 40, 80) + + return ( + + + + {navData.label} + {!!parentWidth && ( + + + + + + + {isAps && ( + + + + )} + + )} + + + + + + + ) + }, +) diff --git a/src/components/Bookmarks/Bookmark/Menu/index.jsx b/src/components/Bookmarks/Bookmark/Menu/index.jsx new file mode 100644 index 0000000000..1cb64891f6 --- /dev/null +++ b/src/components/Bookmarks/Bookmark/Menu/index.jsx @@ -0,0 +1,132 @@ +import { + memo, + useState, + useCallback, + useMemo, + useContext, + useRef, + useEffect, +} from 'react' +import IconButton from '@mui/material/IconButton' +import MuiMenu from '@mui/material/Menu' +import MenuItem from '@mui/material/MenuItem' +import { BsCaretDown } from 'react-icons/bs' +import styled from '@emotion/styled' +import { useResizeDetector } from 'react-resize-detector' +import { observer } from 'mobx-react-lite' +import { motion } from 'framer-motion' + +import { Item } from './Item.jsx' +import { Title } from './Title/index.jsx' +import { MobxContext } from '../../../../mobxContext.js' +import { menuIsInActiveNodePath } from './menuIsInActiveNodePath.js' +import { usePrevious } from '../../../../modules/usePrevious.js' + +const StyledIconButton = styled(IconButton)` + z-index: 2; +` +const StyledMenu = styled(MuiMenu)` + container-type: inline-size; + .MuiPaper-root { + scrollbar-width: thin !important; + min-width: ${(props) => (props.minwidth ? `${props.minwidth}px` : 'unset')}; + } + .MuiList-root { + padding-top: 0; + } +` + +// do NOT use a MenuList. Reason: grabs key input to navigate to menu items +// thus filter input does not work + +export const Menu = memo( + observer(({ navData }) => { + const store = useContext(MobxContext) + const { nodeLabelFilter, activeFilterTable, activeNodeArray } = store.tree + const filterValue = nodeLabelFilter?.[activeFilterTable] ?? '' + + const [anchorEl, setAnchorEl] = useState(null) + const previousAnchorEl = usePrevious(anchorEl) + const open = Boolean(anchorEl) + const onClick = useCallback((event) => setAnchorEl(event.currentTarget), []) + const onClose = useCallback(() => setAnchorEl(null), []) + + const iconId = `${navData.id}/MenuIcon` + const menuId = `${navData.id}/Menu` + + const { width, ref } = useResizeDetector({ + handleHeight: false, + refreshMode: 'debounce', + refreshRate: 300, + refreshOptions: { leading: false, trailing: true }, + }) + + const [filterInputIsVisible, setFilterInputIsVisible] = + useState(!!filterValue) + const filterInputRef = useRef(null) + const toggleFilterInput = useCallback(() => { + if (filterInputIsVisible) { + setFilterInputIsVisible(false) + } else { + setFilterInputIsVisible(true) + setTimeout(() => filterInputRef?.current?.focus?.(), 0) + } + }, [filterInputIsVisible]) + const [titleWidth, setTitleWidth] = useState(0) + + return ( + <> + + + + + + <motion.div + ref={ref} + minwidth={titleWidth} + style={{ minWidth: titleWidth ?? 'unset' }} + initial={{ + marginTop: + previousAnchorEl === null ? 40 + : filterInputIsVisible ? 40 + : 100, + }} + animate={{ marginTop: filterInputIsVisible ? 100 : 40 }} + transition={{ duration: 0.2, delay: 0, ease: 'linear' }} + > + {navData.menus.map((menu) => ( + <Item + key={menu.id} + menu={menu} + baseUrl={navData.url} + onClose={onClose} + /> + ))} + </motion.div> + </StyledMenu> + </> + ) + }), +) diff --git a/src/components/Bookmarks/Bookmark/Menu/menuIsInActiveNodePath.js b/src/components/Bookmarks/Bookmark/Menu/menuIsInActiveNodePath.js new file mode 100644 index 0000000000..5cbc315a2c --- /dev/null +++ b/src/components/Bookmarks/Bookmark/Menu/menuIsInActiveNodePath.js @@ -0,0 +1,11 @@ +import isEqual from 'lodash/isEqual' + +export const menuIsInActiveNodePath = ({ menuUrl, activeNodeArray }) => { + if (!menuUrl) return false + if (!activeNodeArray) return false + const activeNodeArrayPartWithEqualLength = activeNodeArray.slice( + 0, + menuUrl.length, + ) + return isEqual(activeNodeArrayPartWithEqualLength, menuUrl) +} diff --git a/src/components/Bookmarks/Bookmark/index.jsx b/src/components/Bookmarks/Bookmark/index.jsx new file mode 100644 index 0000000000..d65401b52f --- /dev/null +++ b/src/components/Bookmarks/Bookmark/index.jsx @@ -0,0 +1,100 @@ +import { memo, useRef } from 'react' +import { Menu } from './Menu/index.jsx' +import styled from '@emotion/styled' +import { Transition } from 'react-transition-group' +import { useAtom } from 'jotai' + +import { Label } from './Label.jsx' +import { showBookmarksMenuAtom } from '../../../JotaiStore/index.js' + +const OuterContainer = styled.div` + position: relative; + &::after, + &::before { + background: rgb(255, 253, 231); + bottom: 0; + clip-path: polygon(50% 50%, -50% -50%, 0 100%); + content: ''; + left: 100%; + position: absolute; + top: 0; + transition: background 0.2s linear; + width: 2em; + z-index: 1; + } + padding-left: 25px; + &:last-of-type { + padding-left: 10px; + } + &:first-of-type { + margin-right: 15px; + } +` +const Container = styled.div` + margin-right: -10px; + position: relative; + border-collapse: collapse; + transition: background 0.2s linear; + max-width: 45vw; + height: 40px; + padding-right: ${(props) => props.paddingright}; + display: flex; + flex-direction: row; + align-items: stretch; + &::after, + &::before { + background: rgb(46, 125, 50); + bottom: 0; + clip-path: polygon(50% 50%, -50% -50%, 0 100%); + content: ''; + left: calc(100% - 8.3px); + position: absolute; + top: 0.5px; + transition: background 0.2s linear; + width: 2em; + z-index: 1; + } + &::before { + background: rgb(255, 253, 231); + margin-left: 1px; + } +` + +const transitionStyles = { + entering: { opacity: 1 }, + entered: { opacity: 1 }, + exiting: { opacity: 0 }, + exited: { opacity: 0 }, +} + +export const Bookmark = memo(({ navData, in: inProp }) => { + const [showBookmarksMenu] = useAtom(showBookmarksMenuAtom) + + const outerContainerRef = useRef(null) + const labelRef = useRef(null) + + // don't add tooltip on mobile as longpress opens menu + return ( + <Transition + in={inProp} + timeout={700} + mountOnEnter + unmountOnExit + nodeRef={labelRef} + > + {(state) => ( + <OuterContainer ref={outerContainerRef}> + <Container paddingright={showBookmarksMenu ? 'unset' : '15px'}> + <Label + navData={navData} + outerContainerRef={outerContainerRef} + ref={labelRef} + labelStyle={transitionStyles[state]} + /> + {!!navData.menus && showBookmarksMenu && <Menu navData={navData} />} + </Container> + </OuterContainer> + )} + </Transition> + ) +}) diff --git a/src/components/Bookmarks/Bookmarks/Fetcher.jsx b/src/components/Bookmarks/Bookmarks/Fetcher.jsx new file mode 100644 index 0000000000..ca381d6fc7 --- /dev/null +++ b/src/components/Bookmarks/Bookmarks/Fetcher.jsx @@ -0,0 +1,31 @@ +import { memo, useEffect, useState } from 'react' + +import { Spinner } from '../../shared/Spinner.jsx' +import { Error } from '../../shared/Error.jsx' +import { Bookmark } from '../Bookmark/index.jsx' + +// pass on TransitionGroup's props +export const Fetcher = memo(({ match, fetcherModule, ...other }) => { + const fetcherName = match.handle?.bookmarkFetcherName + + // need to pass in params + // If not: When navigating up the tree while transitioning out lower levels, + // those bookmark components will not have their params anymore and error + const params = { ...match.params } + // there is a weird * param containing the pathname. Remove it + delete params['*'] + + const { navData, isLoading, error } = fetcherModule?.[fetcherName](params) + + if (isLoading) return <Spinner /> + + if (error) return <Error error={error} /> + + return ( + <Bookmark + key={`${navData.id}`} + navData={navData} + {...other} + /> + ) +}) diff --git a/src/components/Bookmarks/Bookmarks/FetcherImporter.jsx b/src/components/Bookmarks/Bookmarks/FetcherImporter.jsx new file mode 100644 index 0000000000..8420f8c546 --- /dev/null +++ b/src/components/Bookmarks/Bookmarks/FetcherImporter.jsx @@ -0,0 +1,28 @@ +import { memo, useState, useEffect } from 'react' + +import { Fetcher } from './Fetcher.jsx' +import { Spinner } from '../../shared/Spinner.jsx' + +// pass on TransitionGroup's props +export const FetcherImporter = memo(({ match, ...other }) => { + const [fetcherModule, setFetcherModule] = useState(null) + + const fetcherName = match.handle?.bookmarkFetcherName + + useEffect(() => { + // return the module, not the hook as that would already be called + import(`../../../modules/${fetcherName}.js`).then((module) => { + setFetcherModule(module) + }) + }, [fetcherName]) + + if (!fetcherModule) return <Spinner /> + + return ( + <Fetcher + match={match} + fetcherModule={fetcherModule} + {...other} + /> + ) +}) diff --git a/src/components/Bookmarks/Bookmarks/index.jsx b/src/components/Bookmarks/Bookmarks/index.jsx new file mode 100644 index 0000000000..a339ab0976 --- /dev/null +++ b/src/components/Bookmarks/Bookmarks/index.jsx @@ -0,0 +1,50 @@ +import { memo, useMemo, useEffect, useState } from 'react' +import { useMatches } from 'react-router' +import styled from '@emotion/styled' +import { TransitionGroup } from 'react-transition-group' + +import { FetcherImporter } from './FetcherImporter.jsx' +import { usePrevious } from '../../../modules/usePrevious.js' + +const Container = styled.nav` + display: flex; + flex-direction: row-reverse; + align-items: center; + justify-content: flex-start; + flex-wrap: nowrap; + fex-grow: 1; + flex-shrink: 0; + padding: 0 3px; + min-height: 40px; + border-bottom: rgba(46, 125, 50, 0.5) solid 1px; + overflow-x: auto; + overflow-y: hidden; + scrollbar-width: thin; +` + +const matchesFromAllMatches = (allMatches) => + allMatches + .filter((m) => m.handle?.bookmarkFetcher && m.handle?.bookmarkFetcherName) + .reverse() + +// this component extracts matches +export const Bookmarks = memo(() => { + const allMatches = useMatches() + + const matches = useMemo(() => matchesFromAllMatches(allMatches), [allMatches]) + + // flex-direction row-reverse combined with reverse order of matches + // to align bookmarks to the right, but still have them in order + return ( + <Container> + <TransitionGroup component={null}> + {matches.map((match) => ( + <FetcherImporter + key={match.id} + match={match} + /> + ))} + </TransitionGroup> + </Container> + ) +}) diff --git a/src/components/Bookmarks/MenuBar.jsx b/src/components/Bookmarks/MenuBar.jsx deleted file mode 100644 index b6ea58abc0..0000000000 --- a/src/components/Bookmarks/MenuBar.jsx +++ /dev/null @@ -1,276 +0,0 @@ -import { - memo, - useMemo, - useRef, - useState, - useEffect, - useCallback, - Children, - cloneElement, -} from 'react' -import { IconButton, Menu, MenuItem } from '@mui/material' -import Tooltip from '@mui/material/Tooltip' -import { - FaBars, - FaAlignLeft, - FaAlignRight, - FaArrowDown, - FaArrowRight, - FaArrowLeft, - FaArrowUp, - FaCaretDown, - FaCaretLeft, - FaCaretRight, - FaCaretUp, -} from 'react-icons/fa6' -import styled from '@emotion/styled' -import { over, set } from 'lodash' -import { useDebouncedCallback } from 'use-debounce' - -const buttonHeight = 40 -export const buttonWidth = 40 - -const MeasuredOuterContainer = styled.div` - overflow: hidden; - min-height: ${buttonHeight}px; - max-height: ${buttonHeight}px; - min-width: ${buttonWidth}; - flex-basis: ${buttonWidth}px; - display: flex; - flex-direction: row; - flex-wrap: nowrap; - justify-content: flex-end; - flex-grow: 1; - flex-shrink: 0; - column-gap: 0; - // needed (not transparent) as in fullscreen backed by black - background-color: ${(props) => props.bgColor}; - border-top: 1px solid rgba(46, 125, 50, 0.15); - border-bottom: 1px solid rgba(46, 125, 50, 0.15); -` -// StylingContainer overflows parent????!!!! -// Possible solution: pass in max-width -const StylingContainer = styled.div` - display: flex; - flex-direction: row; - align-items: center; - justify-content: flex-end; - flex-grow: 1; - flex-shrink: 0; - flex-wrap: nowrap; - column-gap: 0; - min-height: ${buttonHeight}px; - max-height: ${buttonHeight}px; - max-width: ${(props) => props.width}px; - overflow: hidden; -` -// remove the margin mui adds to top and bottom of menu -const StyledMenu = styled(Menu)` - background-color: ${(props) => props.bgColor}; - overflow: hidden; - ul { - padding: 0 !important; - } -` -const MenuContent = styled.div` - display: flex; - flex-wrap: wrap; - row-gap: 3px; - background-color: ${(props) => props.bgColor}; - overflow: hidden; - padding: 3px !important; -` - -// possible improvement: -// add refs in here to measure their widths -export const MenuBar = memo( - ({ - children, - // enable the parent to force rerenders - rerenderer, - // files pass in titleComponent and its width - titleComponent, - titleComponentWidth, - bgColor = '#388e3c', - color = 'white', - // top menu bar has no margin between menus, others do - // and that needs to be compensated for - addMargin = true, - }) => { - const { visibleChildren, widths } = useMemo(() => { - const visibleChildren = [] - for (const [index, child] of Children.toArray(children).entries()) { - visibleChildren.push(child) - } - // add 12px for margin and border width to props.width - const widths = visibleChildren.map((c) => - c.props.width ? - addMargin ? c.props.width + 12 - : c.props.width - : buttonWidth, - ) - return { visibleChildren, widths } - }, [children]) - - const outerContainerRef = useRef(null) - const outerContainerWidth = outerContainerRef.current?.clientWidth - const previousMeasurementTimeRef = useRef(0) - - const [buttons, setButtons] = useState([]) - const [menus, setMenus] = useState([]) - - // this was quite some work to get right - // overflowing should only be changed as rarely as possible to prevent unnecessary rerenders - const checkOverflow = useCallback(() => { - if (!outerContainerRef.current) return - - const containerWidth = outerContainerRef.current?.clientWidth - - const titleWidth = titleComponentWidth ?? 0 - const spaceForButtonsAndMenus = containerWidth - titleWidth - const widthOfAllPassedInButtons = - widths ? - widths.reduce((acc, w) => acc + w, 0) - : visibleChildren.length * buttonWidth - const needMenu = widthOfAllPassedInButtons > spaceForButtonsAndMenus - const spaceForButtons = - needMenu ? - spaceForButtonsAndMenus - buttonWidth - : spaceForButtonsAndMenus - // sum widths fitting into spaceForButtons - const newButtons = [] - const newMenus = [] - let widthSum = 0 - for (const [index, child] of Children.toArray( - visibleChildren, - ).entries()) { - const width = - child.props.width ? - addMargin ? child.props.width + 12 - : child.props.width - : buttonWidth - if (widthSum + width > spaceForButtons) { - newMenus.push(cloneElement(child, { inmenu: 'true' })) - } else { - newButtons.push(cloneElement(child)) - widthSum += width - } - } - setButtons(newButtons) - setMenus(newMenus) - // console.log('MenuBar.checkOverflow', { - // widths, - // visibleChildren, - // needMenu, - // spaceForButtonsAndMenus, - // containerWidth, - // titleWidth, - // spaceForButtons, - // newButtons, - // newMenus, - // }) - }, [titleComponentWidth, buttonWidth, widths, visibleChildren, addMargin]) - - const checkOverflowDebounced = useDebouncedCallback(checkOverflow, 300, { - leading: false, - trailing: true, - maxWait: 500, - }) - - useEffect(() => { - // check overflow when rerenderer changes - // Example: file preview (any action that changes the menus passed in) - checkOverflow() - }, [rerenderer]) - - const previousWidthRef = useRef(null) - useEffect(() => { - if (!outerContainerRef.current) { - // console.log('MenuBar.useEffect, no containerRef') - return - } - // set up a resize observer for the container - const observer = new ResizeObserver((entries) => { - // there is only a single entry... - for (const entry of entries) { - const width = entry.contentRect.width - - // only go on if enough time has past since the last measurement (prevent unnecessary rerenders) - const currentTime = Date.now() - const timeSinceLastMeasurement = - currentTime - previousMeasurementTimeRef.current - if (timeSinceLastMeasurement < 300) { - // console.log('MenuBar.resizeObserver, not enough time has passed') - return - } - - // only go on if the width has changed enough (prevent unnecessary rerenders) - // this is the reason for not using react-resize-detector - previousMeasurementTimeRef.current = currentTime - const percentageChanged = Math.abs( - ((width - previousWidthRef.current) / width) * 100, - ) - const shouldCheckOverflow = Math.abs(percentageChanged) > 1 - if (!shouldCheckOverflow) { - // console.log('MenuBar.resizeObserver, not enough change') - return - } - - previousWidthRef.current = width - // console.log('MenuBar.resizeObserver, calling checkOverflowDebounced') - checkOverflowDebounced() - } - }) - - observer.observe(outerContainerRef.current) - - return () => { - // console.log('MenuBar.useEffect, observer.disconnect') - observer.disconnect() - } - }, [rerenderer, checkOverflowDebounced]) - - const onClickMenuButton = useCallback((event) => - setMenuAnchorEl(event.currentTarget), - ) - const onCloseMenu = useCallback(() => setMenuAnchorEl(null), []) - const [menuAnchorEl, setMenuAnchorEl] = useState(null) - const menuIsOpen = Boolean(menuAnchorEl) - - return ( - <MeasuredOuterContainer - ref={outerContainerRef} - bgColor={bgColor} - > - {titleComponent} - <StylingContainer - width={ - Math.abs(outerContainerWidth ?? 0) - (titleComponentWidth ?? 0) - } - > - {buttons} - {!!menus.length && ( - <> - <Tooltip title="Mehr Befehle"> - <IconButton - id="menubutton" - onClick={onClickMenuButton} - > - <FaBars style={{ color }} /> - </IconButton> - </Tooltip> - <StyledMenu - id="menubutton" - anchorEl={menuAnchorEl} - open={menuIsOpen} - onClose={onCloseMenu} - > - <MenuContent bgColor={bgColor}>{menus}</MenuContent> - </StyledMenu> - </> - )} - </StylingContainer> - </MeasuredOuterContainer> - ) - }, -) diff --git a/src/components/Bookmarks/NavTo/Nav.jsx b/src/components/Bookmarks/NavTo/Nav.jsx new file mode 100644 index 0000000000..b49bfa8b9e --- /dev/null +++ b/src/components/Bookmarks/NavTo/Nav.jsx @@ -0,0 +1,41 @@ +import { memo } from 'react' +import { Link, useLocation } from 'react-router' +import Tooltip from '@mui/material/Tooltip' +import styled from '@emotion/styled' + +const StyledLink = styled(Link)` + text-decoration: none; + padding: 0 9px; + min-width: 50px; + max-width: 150px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + border-right: ${(props) => + props.borderright === 'true' ? 'rgba(46, 125, 50, 0.5) solid 1px' : 'none'}; + &:hover { + text-decoration: underline; + text-decoration-color: rgba(55, 118, 28, 0.5); + } +` + +export const Nav = memo(({ item, baseUrl, needsBorderRight = false }) => { + const { pathname, search } = useLocation() + + // issue: relative paths are not working!!!??? + const pathnameWithoutLastSlash = pathname.replace(/\/$/, '') + + return ( + <Tooltip title={item.label}> + <StyledLink + to={{ + pathname: `${baseUrl ?? pathnameWithoutLastSlash}/${item.id}`, + search, + }} + borderright={needsBorderRight.toString()} + > + {item.label} + </StyledLink> + </Tooltip> + ) +}) diff --git a/src/components/Bookmarks/NavTo/Navs/Daten.jsx b/src/components/Bookmarks/NavTo/Navs/Daten.jsx new file mode 100644 index 0000000000..79fbb8d538 --- /dev/null +++ b/src/components/Bookmarks/NavTo/Navs/Daten.jsx @@ -0,0 +1,23 @@ +import { memo } from 'react' + +import { useRootNavData } from '../../../../modules/useRootNavData.js' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' +import { Nav } from '../Nav.jsx' + +export const Menu = memo(() => { + const { navData, isLoading, error } = useRootNavData() + + if (isLoading) return <Spinner /> + + if (error) return <Error error={error} /> + + return navData.menus.map((item, index) => ( + <Nav + key={item.id} + item={item} + baseUrl={navData.url} + needsBorderRight={index < navData.menus.length - 1} + /> + )) +}) diff --git a/src/components/Bookmarks/NavTo/Navs/Projects.jsx b/src/components/Bookmarks/NavTo/Navs/Projects.jsx new file mode 100644 index 0000000000..54b5e7b90e --- /dev/null +++ b/src/components/Bookmarks/NavTo/Navs/Projects.jsx @@ -0,0 +1,27 @@ +import { memo } from 'react' + +import { Nav } from '../Nav.jsx' + +export const navData = { + id: 'projekte', + url: '/Daten/Projekte', + label: `Projekte`, + totalCount: 1, + menus: [ + { + id: 'e57f56f4-4376-11e8-ab21-4314b6749d13', + label: `AP Flora Kt. Zürich`, + }, + ], +} + +export const Menu = memo(() => { + return navData.menus.map((item, index) => ( + <Nav + key={item.id} + item={item} + baseUrl={navData.url} + needsBorderRight={index < navData.menus.length - 1} + /> + )) +}) diff --git a/src/components/Bookmarks/NavTo/index.jsx b/src/components/Bookmarks/NavTo/index.jsx new file mode 100644 index 0000000000..e39accf9d6 --- /dev/null +++ b/src/components/Bookmarks/NavTo/index.jsx @@ -0,0 +1,41 @@ +import { memo } from 'react' +import { useMatches, useLocation } from 'react-router' +import styled from '@emotion/styled' + +const Container = styled.nav` + display: flex; + align-items: center; + justify-content: flex-start; + flex-wrap: nowrap; + fex-grow: 1; + flex-shrink: 0; + padding: 0 3px; + height: 40px; + min-height: 40px; + border-bottom: rgba(46, 125, 50, 0.5) solid 1px; + overflow-x: overlay; + scrollbar-width: thin; +` + +export const NavTo = memo(() => { + const { pathname } = useLocation() + const allMatches = useMatches() + // get match that contains the current pathname minus the last slash - if it ends with a slash + // Hm. So many matches. Often multiple with same path. Hard to find the right one. + // TODO: ensure this works for all cases + const navMatches = allMatches.filter( + (m) => + (m.pathname === pathname || `${m.pathname}/` === pathname) && + m.handle?.nav, + ) + const navMatch = navMatches?.[0] + const Nav = navMatch?.handle?.nav + + return ( + <Container> + {!!Nav ? + <Nav /> + : null} + </Container> + ) +}) diff --git a/src/components/Bookmarks/index.jsx b/src/components/Bookmarks/index.jsx index 721ef665f9..9110de428b 100644 --- a/src/components/Bookmarks/index.jsx +++ b/src/components/Bookmarks/index.jsx @@ -1,5 +1,14 @@ import { memo } from 'react' +import { useMatches, useLocation } from 'react-router' -export const Bookmarks = memo(() => { - return <div>Bookmarks</div> -}) +import { NavTo } from './NavTo/index.jsx' +import { Bookmarks as Bookmarkss } from './Bookmarks/index.jsx' + +// this file is bypassed for now +// as NavTo is not needed +export const Bookmarks = memo(() => ( + <> + <Bookmarkss /> + {/* <NavTo /> */} + </> +)) diff --git a/src/components/Deletions/index.jsx b/src/components/Deletions/index.jsx index 53d5fd6819..8887f06546 100644 --- a/src/components/Deletions/index.jsx +++ b/src/components/Deletions/index.jsx @@ -13,7 +13,7 @@ import { observer } from 'mobx-react-lite' import { useApolloClient } from '@apollo/client' import { undelete } from './undelete/index.jsx' -import { StoreContext } from '../../storeContext.js' +import { MobxContext } from '../../mobxContext.js' import { ErrorBoundary } from '../shared/ErrorBoundary.jsx' const List = styled.div` @@ -53,7 +53,7 @@ const StyledCheckbox = styled(Checkbox)` export const Deletions = memo( observer(() => { const client = useApolloClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { removeDeletedDatasetById, deletedDatasets, diff --git a/src/components/Docs/Sidebar/MenuItems.jsx b/src/components/Docs/Sidebar/MenuItems.jsx index e3a17b00de..baa9562e0e 100644 --- a/src/components/Docs/Sidebar/MenuItems.jsx +++ b/src/components/Docs/Sidebar/MenuItems.jsx @@ -4,7 +4,7 @@ import Divider from '@mui/material/Divider' import styled from '@emotion/styled' import { MenuItem } from './MenuItem.jsx' -import { StoreContext } from '../../../storeContext.js' +import { MobxContext } from '../../../mobxContext.js' // don't know why but divider is too thick, // thicker than ListItemButton divider @@ -157,7 +157,7 @@ const nodes = [ ] export const MenuItems = () => { - const { dokuFilter } = useContext(StoreContext) + const { dokuFilter } = useContext(MobxContext) const nodesFiltered = nodes.filter( (node) => node.title?.toLowerCase?.()?.includes?.(dokuFilter) ?? true, ) diff --git a/src/components/Docs/Sidebar/index.jsx b/src/components/Docs/Sidebar/index.jsx index 6523c31030..34918df214 100644 --- a/src/components/Docs/Sidebar/index.jsx +++ b/src/components/Docs/Sidebar/index.jsx @@ -3,7 +3,7 @@ import styled from '@emotion/styled' import { Link, useLocation } from 'react-router' import { observer } from 'mobx-react-lite' -import { StoreContext } from '../../../storeContext.js' +import { MobxContext } from '../../../mobxContext.js' import { MenuItems } from './MenuItems.jsx' import { Filter } from './Filter.jsx' import { IntoViewScroller } from './IntoViewScroller.jsx' @@ -35,7 +35,7 @@ export const Sidebar = memo( observer(() => { const { search } = useLocation() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { dokuFilter, setDokuFilter } = store return ( diff --git a/src/components/EkPlan/ApList/Ap/index.jsx b/src/components/EkPlan/ApList/Ap/index.jsx index 4e551cc8a6..b70e3517fb 100644 --- a/src/components/EkPlan/ApList/Ap/index.jsx +++ b/src/components/EkPlan/ApList/Ap/index.jsx @@ -4,7 +4,7 @@ import IconButton from '@mui/material/IconButton' import Tooltip from '@mui/material/Tooltip' import styled from '@emotion/styled' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' const Container = styled.div` display: flex; @@ -27,7 +27,7 @@ const DelIcon = styled(IconButton)` ` export const Ap = ({ ap }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { removeAp, apsData, apsDataLoading } = store.ekPlan const onClickDelete = useCallback(() => removeAp(ap), [ap, removeAp]) diff --git a/src/components/EkPlan/ApList/ChooseAp/index.jsx b/src/components/EkPlan/ApList/ChooseAp/index.jsx index 16724014b6..8ad7558d02 100644 --- a/src/components/EkPlan/ApList/ChooseAp/index.jsx +++ b/src/components/EkPlan/ApList/ChooseAp/index.jsx @@ -6,7 +6,7 @@ import { observer } from 'mobx-react-lite' import { useParams } from 'react-router' import { queryApsToChoose } from './queryApsToChoose.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' const StyledSelect = styled(AsyncSelect)` @@ -76,7 +76,7 @@ export const ChooseAp = memo( observer(({ setShowChoose }) => { const { projId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { aps, addAp } = store.ekPlan const client = useApolloClient() diff --git a/src/components/EkPlan/ApList/index.jsx b/src/components/EkPlan/ApList/index.jsx index c7c0cc6dd6..f753e28190 100644 --- a/src/components/EkPlan/ApList/index.jsx +++ b/src/components/EkPlan/ApList/index.jsx @@ -6,7 +6,7 @@ import styled from '@emotion/styled' import { Ap } from './Ap/index.jsx' import { ChooseAp } from './ChooseAp/index.jsx' -import { StoreContext } from '../../../storeContext.js' +import { MobxContext } from '../../../mobxContext.js' const Container = styled.div` display: flex; @@ -31,7 +31,7 @@ const PlusIcon = styled(IconButton)` ` export const ApList = () => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { aps } = store.ekPlan const [showChoose, setShowChoose] = useState(aps.length === 0) diff --git a/src/components/EkPlan/Choose.jsx b/src/components/EkPlan/Choose.jsx index a0f163615d..0e69657f27 100644 --- a/src/components/EkPlan/Choose.jsx +++ b/src/components/EkPlan/Choose.jsx @@ -17,7 +17,7 @@ import DialogTitle from '@mui/material/DialogTitle' import { observer } from 'mobx-react-lite' import { Fields } from './Fields.jsx' -import { StoreContext } from '../../storeContext.js' +import { MobxContext } from '../../mobxContext.js' import { allFields } from '../../store/EkPlan/index.js' import { ErrorBoundary } from '../shared/ErrorBoundary.jsx' @@ -82,7 +82,7 @@ const DenserCheckbox = (props) => ( export const Choose = memo( observer(() => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { fields, showEk, diff --git a/src/components/EkPlan/Fields.jsx b/src/components/EkPlan/Fields.jsx index acb00255e7..1255664454 100644 --- a/src/components/EkPlan/Fields.jsx +++ b/src/components/EkPlan/Fields.jsx @@ -5,7 +5,7 @@ import FormControlLabel from '@mui/material/FormControlLabel' import Radio from '@mui/material/Radio' import DialogContent from '@mui/material/DialogContent' -import { StoreContext } from '../../storeContext.js' +import { MobxContext } from '../../mobxContext.js' import { ErrorBoundary } from '../shared/ErrorBoundary.jsx' const StyledDialogContent = styled(DialogContent)` @@ -15,7 +15,7 @@ const StyledDialogContent = styled(DialogContent)` export const Fields = memo( observer(() => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { fields, toggleField } = store.ekPlan return ( diff --git a/src/components/EkPlan/Table/CellForEkfrequenz.jsx b/src/components/EkPlan/Table/CellForEkfrequenz.jsx index 2f614a542f..5f652ca3e1 100644 --- a/src/components/EkPlan/Table/CellForEkfrequenz.jsx +++ b/src/components/EkPlan/Table/CellForEkfrequenz.jsx @@ -7,7 +7,7 @@ import max from 'lodash/max' import { StyledCellForSelect } from './index.jsx' import { tpop } from '../../shared/fragments.js' -import { StoreContext } from '../../../storeContext.js' +import { MobxContext } from '../../../mobxContext.js' import { setStartjahr } from './setStartjahr/index.jsx' import { setEkplans } from './setEkplans/index.jsx' @@ -31,7 +31,7 @@ const Option = styled.option` export const CellForEkfrequenz = memo( observer(({ row, field, style, refetchTpop, ekfrequenzs }) => { const client = useApolloClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { enqueNotification } = store const { hovered } = store.ekPlan const className = hovered.tpopId === row.id ? 'tpop-hovered' : '' diff --git a/src/components/EkPlan/Table/CellForEkfrequenzAbweichend.jsx b/src/components/EkPlan/Table/CellForEkfrequenzAbweichend.jsx index 8845c86729..20e687096c 100644 --- a/src/components/EkPlan/Table/CellForEkfrequenzAbweichend.jsx +++ b/src/components/EkPlan/Table/CellForEkfrequenzAbweichend.jsx @@ -3,11 +3,11 @@ import { observer } from 'mobx-react-lite' import { Checkbox } from './Checkbox.jsx' import { StyledCellForSelect } from './index.jsx' -import { StoreContext } from '../../../storeContext.js' +import { MobxContext } from '../../../mobxContext.js' export const CellForEkfrequenzAbweichend = memo( observer(({ field, row, style }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { hovered } = store.ekPlan const className = hovered.tpopId === row.id ? 'tpop-hovered' : '' diff --git a/src/components/EkPlan/Table/CellForEkfrequenzStartjahr.jsx b/src/components/EkPlan/Table/CellForEkfrequenzStartjahr.jsx index 18e893959f..5ba81386e4 100644 --- a/src/components/EkPlan/Table/CellForEkfrequenzStartjahr.jsx +++ b/src/components/EkPlan/Table/CellForEkfrequenzStartjahr.jsx @@ -3,7 +3,7 @@ import { observer } from 'mobx-react-lite' import { useApolloClient, gql } from '@apollo/client' import styled from '@emotion/styled' -import { StoreContext } from '../../../storeContext.js' +import { MobxContext } from '../../../mobxContext.js' import { tpop } from '../../shared/fragments.js' import { setEkplans } from './setEkplans/index.jsx' @@ -60,7 +60,7 @@ const Input = styled.input` export const CellForEkfrequenzStartjahr = memo( observer(({ row, style, refetchTpop }) => { const client = useApolloClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { enqueNotification } = store const { hovered } = store.ekPlan const className = hovered.tpopId === row.id ? 'tpop-hovered' : '' diff --git a/src/components/EkPlan/Table/CellForTpopLink.jsx b/src/components/EkPlan/Table/CellForTpopLink.jsx index a65e13017e..dc68a3067a 100644 --- a/src/components/EkPlan/Table/CellForTpopLink.jsx +++ b/src/components/EkPlan/Table/CellForTpopLink.jsx @@ -4,7 +4,7 @@ import { observer } from 'mobx-react-lite' import styled from '@emotion/styled' import { StyledTableCell } from './index.jsx' -import { StoreContext } from '../../../storeContext.js' +import { MobxContext } from '../../../mobxContext.js' const Link = styled.div` cursor: pointer; @@ -23,7 +23,7 @@ const Link = styled.div` export const CellForTpopLink = memo( observer(({ field, style, row }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { hovered } = store.ekPlan const className = hovered.tpopId === row.id ? 'tpop-hovered' : '' diff --git a/src/components/EkPlan/Table/CellForValue.jsx b/src/components/EkPlan/Table/CellForValue.jsx index a3e248b7ac..400e1c81af 100644 --- a/src/components/EkPlan/Table/CellForValue.jsx +++ b/src/components/EkPlan/Table/CellForValue.jsx @@ -3,7 +3,7 @@ import { observer } from 'mobx-react-lite' import styled from '@emotion/styled' import { StyledTableCell } from './index.jsx' -import { StoreContext } from '../../../storeContext.js' +import { MobxContext } from '../../../mobxContext.js' const Container = styled.div` height: 100%; @@ -14,7 +14,7 @@ const Container = styled.div` export const CellForValue = memo( observer(({ field, style, row, firstChild }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { value } = field diff --git a/src/components/EkPlan/Table/CellForYear/EkIcon.jsx b/src/components/EkPlan/Table/CellForYear/EkIcon.jsx index 5dc2938c40..53b627d138 100644 --- a/src/components/EkPlan/Table/CellForYear/EkIcon.jsx +++ b/src/components/EkPlan/Table/CellForYear/EkIcon.jsx @@ -3,7 +3,7 @@ import styled from '@emotion/styled' import sum from 'lodash/sum' import { observer } from 'mobx-react-lite' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' const CheckboxContainer = styled.div` width: 100%; @@ -50,7 +50,7 @@ const SumCounted = styled.div` export const EkIcon = memo( observer(({ planned, eks, einheits }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { showCount, showEkCount } = store.ekPlan //console.log('EkIcon', { planned, eks, einheits }) diff --git a/src/components/EkPlan/Table/CellForYear/MassnIcon.jsx b/src/components/EkPlan/Table/CellForYear/MassnIcon.jsx index 86a84f35da..6d1fe7b085 100644 --- a/src/components/EkPlan/Table/CellForYear/MassnIcon.jsx +++ b/src/components/EkPlan/Table/CellForYear/MassnIcon.jsx @@ -4,7 +4,7 @@ import sum from 'lodash/sum' import { GoZap } from 'react-icons/go' import { observer } from 'mobx-react-lite' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' const CheckboxContainer = styled.div` width: 100%; @@ -44,7 +44,7 @@ const SumCounted = styled.div` export const MassnIcon = memo( observer(({ ansiedlungs }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { showCount, showEkCount } = store.ekPlan if (!ansiedlungs.length) { diff --git a/src/components/EkPlan/Table/CellForYear/index.jsx b/src/components/EkPlan/Table/CellForYear/index.jsx index 6f642eb0cc..9c18ac3eb8 100644 --- a/src/components/EkPlan/Table/CellForYear/index.jsx +++ b/src/components/EkPlan/Table/CellForYear/index.jsx @@ -5,11 +5,11 @@ import { StyledTableCell } from '../index.jsx' import { EkIcon } from './EkIcon.jsx' import { MassnIcon } from './MassnIcon.jsx' import { InfoRow } from '../index.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' export const CellForYear = memo( observer(({ field, row, style }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { showEk, showEkf, diff --git a/src/components/EkPlan/Table/CellForYearMenu/index.jsx b/src/components/EkPlan/Table/CellForYearMenu/index.jsx index 3fed036551..9f2ae14e25 100644 --- a/src/components/EkPlan/Table/CellForYearMenu/index.jsx +++ b/src/components/EkPlan/Table/CellForYearMenu/index.jsx @@ -8,7 +8,7 @@ import styled from '@emotion/styled' import { observer } from 'mobx-react-lite' import { useQuery, useApolloClient } from '@apollo/client' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { queryTpop } from './queryTpop.js' import { queryEkplansOfTpop } from './queryEkplansOfTpop.js' import { mutationCreateEkplan } from './mutationCreateEkplan.js' @@ -48,7 +48,7 @@ const anchorOrigin = { horizontal: 'right', vertical: 'top' } export const CellForYearMenu = memo( observer(() => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const client = useApolloClient() const { showEk, diff --git a/src/components/EkPlan/Table/CellForYearTitle.jsx b/src/components/EkPlan/Table/CellForYearTitle.jsx index f3825d1871..75efa32b6b 100644 --- a/src/components/EkPlan/Table/CellForYearTitle.jsx +++ b/src/components/EkPlan/Table/CellForYearTitle.jsx @@ -2,11 +2,11 @@ import { memo, useContext, useCallback } from 'react' import { observer } from 'mobx-react-lite' import { StyledTableCell, InfoRow } from './index.jsx' -import { StoreContext } from '../../../storeContext.js' +import { MobxContext } from '../../../mobxContext.js' export const CellForYearTitle = memo( observer(({ style, row }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { showEk, showEkf, showMassn, hovered } = store.ekPlan const className = hovered.tpopId === row.id ? 'tpop-hovered' : '' const onMouseEnter = useCallback( diff --git a/src/components/EkPlan/Table/CellHeaderFixed/BooleanFilter.jsx b/src/components/EkPlan/Table/CellHeaderFixed/BooleanFilter.jsx index 2a4e801adf..8e609f5d3c 100644 --- a/src/components/EkPlan/Table/CellHeaderFixed/BooleanFilter.jsx +++ b/src/components/EkPlan/Table/CellHeaderFixed/BooleanFilter.jsx @@ -1,15 +1,15 @@ /* eslint-disable react-hooks/exhaustive-deps */ -import { useCallback, useContext, forwardRef } from 'react' +import { memo, useCallback, useContext } from 'react' import MenuItem from '@mui/material/MenuItem' import upperFirst from 'lodash/upperFirst' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' // need to forward ref from Menu to MenuItem // see: https://github.com/mui-org/material-ui/issues/15903#issuecomment-496313450 // and: https://reactjs.org/docs/forwarding-refs.html -export const BooleanFilter = forwardRef(({ column, closeMenu }, ref) => { - const store = useContext(StoreContext) +export const BooleanFilter = memo(({ column, closeMenu, ref }) => { + const store = useContext(MobxContext) const { name } = column const storeValue = store.ekPlan?.[`filter${upperFirst(name)}`] diff --git a/src/components/EkPlan/Table/CellHeaderFixed/TextFilter.jsx b/src/components/EkPlan/Table/CellHeaderFixed/TextFilter.jsx index 82a0d3fe8a..1f0430a7f6 100644 --- a/src/components/EkPlan/Table/CellHeaderFixed/TextFilter.jsx +++ b/src/components/EkPlan/Table/CellHeaderFixed/TextFilter.jsx @@ -9,7 +9,7 @@ import Tooltip from '@mui/material/Tooltip' import { MdClear } from 'react-icons/md' import upperFirst from 'lodash/upperFirst' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' const valForStore = (valPassed) => { let val = valPassed @@ -24,7 +24,7 @@ const valForState = (valPassed) => { } export const TextFilter = ({ column, closeMenu }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { setFilterEmptyEkfrequenz, setFilterEmptyEkfrequenzStartjahr } = store.ekPlan const { name } = column diff --git a/src/components/EkPlan/Table/CellHeaderFixed/index.jsx b/src/components/EkPlan/Table/CellHeaderFixed/index.jsx index d6d87c17df..b61808b282 100644 --- a/src/components/EkPlan/Table/CellHeaderFixed/index.jsx +++ b/src/components/EkPlan/Table/CellHeaderFixed/index.jsx @@ -7,7 +7,7 @@ import styled from '@emotion/styled' import upperFirst from 'lodash/upperFirst' import { observer } from 'mobx-react-lite' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { TextFilter } from './TextFilter.jsx' import { BooleanFilter } from './BooleanFilter.jsx' @@ -54,7 +54,7 @@ const anchorOrigin = { horizontal: 'left', vertical: 'bottom' } export const CellHeaderFixed = memo( observer(({ style, column }) => { const { name, label, nofilter } = column - const store = useContext(StoreContext) + const store = useContext(MobxContext) const filterValue = store.ekPlan?.[`filter${upperFirst(name)}`] diff --git a/src/components/EkPlan/Table/CellHeaderFixedEkfrequenz.jsx b/src/components/EkPlan/Table/CellHeaderFixedEkfrequenz.jsx index a2a5553071..3fe3264918 100644 --- a/src/components/EkPlan/Table/CellHeaderFixedEkfrequenz.jsx +++ b/src/components/EkPlan/Table/CellHeaderFixedEkfrequenz.jsx @@ -4,7 +4,7 @@ import MenuItem from '@mui/material/MenuItem' import { FaSortDown as Caret, FaFilter } from 'react-icons/fa' import styled from '@emotion/styled' -import { StoreContext } from '../../../storeContext.js' +import { MobxContext } from '../../../mobxContext.js' import { TextFilter } from './CellHeaderFixed/TextFilter.jsx' export const StyledCell = styled.div` @@ -54,7 +54,7 @@ const StyledMenu = styled(Menu)` const anchorOrigin = { horizontal: 'left', vertical: 'bottom' } export const CellHeaderFixedEkfrequenz = ({ style, column }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { filterEkfrequenzEmpty, setFilterEmptyEkfrequenz, diff --git a/src/components/EkPlan/Table/CellHeaderFixedEkfrequenzStartjahr.jsx b/src/components/EkPlan/Table/CellHeaderFixedEkfrequenzStartjahr.jsx index c2162f30b9..0c9809c80a 100644 --- a/src/components/EkPlan/Table/CellHeaderFixedEkfrequenzStartjahr.jsx +++ b/src/components/EkPlan/Table/CellHeaderFixedEkfrequenzStartjahr.jsx @@ -4,7 +4,7 @@ import MenuItem from '@mui/material/MenuItem' import { FaSortDown as Caret, FaFilter } from 'react-icons/fa' import styled from '@emotion/styled' -import { StoreContext } from '../../../storeContext.js' +import { MobxContext } from '../../../mobxContext.js' import { TextFilter } from './CellHeaderFixed/TextFilter.jsx' export const StyledCell = styled.div` @@ -55,7 +55,7 @@ const StyledMenu = styled(Menu)` const anchorOrigin = { horizontal: 'left', vertical: 'bottom' } export const CellHeaderFixedEkfrequenzStartjahr = ({ style, column }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { filterEkfrequenzStartjahrEmpty, setFilterEmptyEkfrequenzStartjahr, diff --git a/src/components/EkPlan/Table/CellHeaderYear.jsx b/src/components/EkPlan/Table/CellHeaderYear.jsx index 60e1774d96..046e2da880 100644 --- a/src/components/EkPlan/Table/CellHeaderYear.jsx +++ b/src/components/EkPlan/Table/CellHeaderYear.jsx @@ -6,7 +6,7 @@ import { FaSortDown as Caret, FaFilter } from 'react-icons/fa' import styled from '@emotion/styled' import { useApolloClient } from '@apollo/client' -import { StoreContext } from '../../../storeContext.js' +import { MobxContext } from '../../../mobxContext.js' const StyledCell = styled.div` display: flex; @@ -45,7 +45,7 @@ export const CellHeaderYear = memo( observer(({ style, column, rows }) => { const client = useApolloClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { hovered, filterAnsiedlungYear, diff --git a/src/components/EkPlan/Table/Checkbox.jsx b/src/components/EkPlan/Table/Checkbox.jsx index 5b59a823f3..5747089760 100644 --- a/src/components/EkPlan/Table/Checkbox.jsx +++ b/src/components/EkPlan/Table/Checkbox.jsx @@ -4,7 +4,7 @@ import { useApolloClient, gql } from '@apollo/client' import styled from '@emotion/styled' import { tpop } from '../../shared/fragments.js' -import { StoreContext } from '../../../storeContext.js' +import { MobxContext } from '../../../mobxContext.js' const CheckboxContainer = styled.div` width: 100%; @@ -32,7 +32,7 @@ const StyledCheckbox = styled.div` export const Checkbox = memo( observer(({ row, value, field }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { enqueNotification } = store const client = useApolloClient() diff --git a/src/components/EkPlan/Table/Select.jsx b/src/components/EkPlan/Table/Select.jsx index a842dd791d..fb9a236a2b 100644 --- a/src/components/EkPlan/Table/Select.jsx +++ b/src/components/EkPlan/Table/Select.jsx @@ -5,7 +5,7 @@ import { observer } from 'mobx-react-lite' import { useApolloClient, gql } from '@apollo/client' import { tpop } from '../../shared/fragments.js' -import { StoreContext } from '../../../storeContext.js' +import { MobxContext } from '../../../mobxContext.js' const Select = styled.select` width: 100%; @@ -26,7 +26,7 @@ const Option = styled.option` export const SelectComponent = memo( observer(({ options, row, val, field }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { enqueNotification } = store const client = useApolloClient() const [focused, setFocused] = useState(false) diff --git a/src/components/EkPlan/Table/index.jsx b/src/components/EkPlan/Table/index.jsx index abbc4b8152..0be1e297c2 100644 --- a/src/components/EkPlan/Table/index.jsx +++ b/src/components/EkPlan/Table/index.jsx @@ -8,7 +8,7 @@ import { FixedSizeGrid, VariableSizeGrid, VariableSizeList } from 'react-window' import Button from '@mui/material/Button' import { useResizeDetector } from 'react-resize-detector' -import { StoreContext } from '../../../storeContext.js' +import { MobxContext } from '../../../mobxContext.js' import { queryAll } from './queryAll.js' import { CellForYearMenu } from './CellForYearMenu/index.jsx' import { yearsFromTpops } from './yearsFromTpops.js' @@ -120,7 +120,7 @@ const ExportButton = styled(Button)` export const EkPlanTable = memo( observer(() => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { aps, apValues, diff --git a/src/components/EkPlan/index.jsx b/src/components/EkPlan/index.jsx index 0a1e272049..c182a4ce93 100644 --- a/src/components/EkPlan/index.jsx +++ b/src/components/EkPlan/index.jsx @@ -14,7 +14,7 @@ const Choose = lazy(async () => ({ default: (await import('./Choose.jsx')).Choose, })) import { queryAps } from './queryAps.js' -import { StoreContext } from '../../storeContext.js' +import { MobxContext } from '../../mobxContext.js' import { appBaseUrl } from '../../modules/appBaseUrl.js' const Error = lazy(async () => ({ default: (await import('../shared/Error.jsx')).Error, @@ -51,7 +51,7 @@ const AnleitungButton = styled(Button)` export const Component = memo( observer(() => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { user } = store const { aps, setApsData, setApsDataLoading } = store.ekPlan diff --git a/src/components/Ekf/index.jsx b/src/components/Ekf/index.jsx index efd0c1d0db..ca5817ee3b 100644 --- a/src/components/Ekf/index.jsx +++ b/src/components/Ekf/index.jsx @@ -9,7 +9,7 @@ import sortBy from 'lodash/sortBy' // but only in production! import { EkfList } from './List/index.jsx' import { Component as Tpopfreiwkontr } from '../Projekte/Daten/Tpopfreiwkontr/index.jsx' -import { StoreContext } from '../../storeContext.js' +import { MobxContext } from '../../mobxContext.js' import { StyledSplitPane } from '../shared/StyledSplitPane' import { dataByUserId as dataByUserIdGql } from './dataByUserId.js' import { dataWithDateByUserId as dataWithDateByUserIdGql } from './dataWithDateByUserId.js' @@ -67,7 +67,7 @@ export const Component = memo( const { search } = useLocation() const navigate = useNavigate() const { userId, ekfId, ekfYear } = useParams() - const { isPrint, isEkfSinglePrint } = useContext(StoreContext) + const { isPrint, isEkfSinglePrint } = useContext(MobxContext) const ekfRefDate = new Date() //.setMonth(new Date().getMonth() - 2) const ekfRefYear = new Date(ekfRefDate).getFullYear() diff --git a/src/components/GlobalStyle.jsx b/src/components/GlobalStyle.jsx index 92894b5235..261082453c 100644 --- a/src/components/GlobalStyle.jsx +++ b/src/components/GlobalStyle.jsx @@ -44,6 +44,12 @@ export const GlobalStyle = () => ( color: black; } + // ensuring menus in more-menu have a a border + // somehow the inmenu prop is not always passed in + .menubar-more-menus button { + border: 1px solid #ab9518 !important; + } + /* scrollbars */ .simplebar-scrollbar:before { diff --git a/src/components/IsPrintSetter.jsx b/src/components/IsPrintSetter.jsx index b5ce73201c..0afb3e779b 100644 --- a/src/components/IsPrintSetter.jsx +++ b/src/components/IsPrintSetter.jsx @@ -1,11 +1,11 @@ import { memo, useContext, useEffect } from 'react' import { observer } from 'mobx-react-lite' -import { StoreContext } from '../storeContext.js' +import { MobxContext } from '../mobxContext.js' export const IsPrintSetter = memo( observer(() => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { setIsPrint } = store /** diff --git a/src/components/LastTouchedNodeSetter.jsx b/src/components/LastTouchedNodeSetter.jsx index ca83e7d525..09d15561f1 100644 --- a/src/components/LastTouchedNodeSetter.jsx +++ b/src/components/LastTouchedNodeSetter.jsx @@ -2,11 +2,11 @@ import { memo, useContext, useEffect } from 'react' import { observer } from 'mobx-react-lite' import { getSnapshot } from 'mobx-state-tree' -import { StoreContext } from '../storeContext.js' +import { MobxContext } from '../mobxContext.js' export const LastTouchedNodeSetter = memo( observer(() => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { activeNodeArray, setLastTouchedNode } = store.tree useEffect(() => { diff --git a/src/components/Messages/Messages/Messages.jsx b/src/components/Messages/Messages/Messages.jsx index ee862487a4..26b686e671 100644 --- a/src/components/Messages/Messages/Messages.jsx +++ b/src/components/Messages/Messages/Messages.jsx @@ -7,7 +7,7 @@ import { observer } from 'mobx-react-lite' import { DateTime } from 'luxon' import { createUsermessage } from '../createUsermessage.js' -import { StoreContext } from '../../../storeContext.js' +import { MobxContext } from '../../../mobxContext.js' const Container = styled.div` height: 100%; @@ -33,7 +33,7 @@ const OkButton = styled(Button)` export const Messages = memo( observer(({ unreadMessages }) => { const client = useApolloClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { user } = store const userName = user.name diff --git a/src/components/Messages/index.jsx b/src/components/Messages/index.jsx index f951f4ffc9..67796c90dc 100644 --- a/src/components/Messages/index.jsx +++ b/src/components/Messages/index.jsx @@ -8,7 +8,7 @@ import { observer } from 'mobx-react-lite' import { query } from './query.js' import { createUsermessage } from './createUsermessage.js' -import { StoreContext } from '../../storeContext.js' +import { MobxContext } from '../../mobxContext.js' import { Error } from '../shared/Error.jsx' import { ErrorBoundary } from '../shared/ErrorBoundary.jsx' import { MessagesList } from './Messages/index.jsx' @@ -35,7 +35,7 @@ const AllOkButton = styled(Button)` export const Messages = memo( observer(() => { const client = useApolloClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { user } = store const userName = user.name diff --git a/src/components/NavElements/BeobnichtbeurteiltFilteredMapIcon.jsx b/src/components/NavElements/BeobnichtbeurteiltFilteredMapIcon.jsx new file mode 100644 index 0000000000..acaa960451 --- /dev/null +++ b/src/components/NavElements/BeobnichtbeurteiltFilteredMapIcon.jsx @@ -0,0 +1,17 @@ +import styled from '@emotion/styled' + +import { MapIcon } from './MapIcon.jsx' +import { IconContainer } from './IconContainer.jsx' + +const StyledMapIcon = styled(MapIcon)` + color: #9a009a !important; + paint-order: stroke; + stroke-width: 8px; + stroke: #fff900; +` + +export const BeobnichtbeurteiltFilteredMapIcon = () => ( + <IconContainer title="Beobachtung in Karte hervorgehoben"> + <StyledMapIcon /> + </IconContainer> +) diff --git a/src/components/NavElements/BeobnichtbeurteiltMapIcon.jsx b/src/components/NavElements/BeobnichtbeurteiltMapIcon.jsx new file mode 100644 index 0000000000..78d4af7312 --- /dev/null +++ b/src/components/NavElements/BeobnichtbeurteiltMapIcon.jsx @@ -0,0 +1,14 @@ +import styled from '@emotion/styled' + +import { MapIcon } from './MapIcon.jsx' +import { IconContainer } from './IconContainer.jsx' + +const StyledMapIcon = styled(MapIcon)` + color: #9a009a !important; +` + +export const BeobnichtbeurteiltMapIcon = () => ( + <IconContainer title="Beobachtungen nicht beurteilt in Karte sichtbar"> + <StyledMapIcon /> + </IconContainer> +) diff --git a/src/components/NavElements/BeobnichtzuzuordnenFilteredMapIcon.jsx b/src/components/NavElements/BeobnichtzuzuordnenFilteredMapIcon.jsx new file mode 100644 index 0000000000..a6e980d8f0 --- /dev/null +++ b/src/components/NavElements/BeobnichtzuzuordnenFilteredMapIcon.jsx @@ -0,0 +1,17 @@ +import styled from '@emotion/styled' + +import { MapIcon } from './MapIcon.jsx' +import { IconContainer } from './IconContainer.jsx' + +const StyledMapIcon = styled(MapIcon)` + color: #ffe4ff !important; + paint-order: stroke; + stroke-width: 8px; + stroke: #fff900; +` + +export const BeobnichtzuzuordnenFilteredMapIcon = () => ( + <IconContainer title="Beobachtung in Karte hervorgehoben"> + <StyledMapIcon /> + </IconContainer> +) diff --git a/src/components/NavElements/BeobnichtzuzuordnenMapIcon.jsx b/src/components/NavElements/BeobnichtzuzuordnenMapIcon.jsx new file mode 100644 index 0000000000..2f650b6292 --- /dev/null +++ b/src/components/NavElements/BeobnichtzuzuordnenMapIcon.jsx @@ -0,0 +1,14 @@ +import styled from '@emotion/styled' + +import { MapIcon } from './MapIcon.jsx' +import { IconContainer } from './IconContainer.jsx' + +const StyledMapIcon = styled(MapIcon)` + color: #ffe4ff !important; +` + +export const BeobnichtzuzuordnenMapIcon = () => ( + <IconContainer title="Beobachtungen nicht zuzuordnen in Karte sichtbar"> + <StyledMapIcon /> + </IconContainer> +) diff --git a/src/components/NavElements/BeobzugeordnetFilteredMapIcon.jsx b/src/components/NavElements/BeobzugeordnetFilteredMapIcon.jsx new file mode 100644 index 0000000000..600e23ee4d --- /dev/null +++ b/src/components/NavElements/BeobzugeordnetFilteredMapIcon.jsx @@ -0,0 +1,17 @@ +import styled from '@emotion/styled' + +import { MapIcon } from './MapIcon.jsx' +import { IconContainer } from './IconContainer.jsx' + +const StyledMapIcon = styled(MapIcon)` + color: #ff00ff !important; + paint-order: stroke; + stroke-width: 8px; + stroke: #fff900; +` + +export const BeobzugeordnetFilteredMapIcon = () => ( + <IconContainer title="Beobachtung in Karte hervorgehoben"> + <StyledMapIcon /> + </IconContainer> +) diff --git a/src/components/NavElements/BeobzugeordnetMapIcon.jsx b/src/components/NavElements/BeobzugeordnetMapIcon.jsx new file mode 100644 index 0000000000..3a272eecc6 --- /dev/null +++ b/src/components/NavElements/BeobzugeordnetMapIcon.jsx @@ -0,0 +1,14 @@ +import styled from '@emotion/styled' + +import { MapIcon } from './MapIcon.jsx' +import { IconContainer } from './IconContainer.jsx' + +const StyledMapIcon = styled(MapIcon)` + color: #ff00ff !important; +` + +export const BeobzugeordnetMapIcon = () => ( + <IconContainer title="Beobachtungen zugeordnet in Karte sichtbar"> + <StyledMapIcon /> + </IconContainer> +) diff --git a/src/components/NavElements/BiotopCopyingIcon.jsx b/src/components/NavElements/BiotopCopyingIcon.jsx new file mode 100644 index 0000000000..94823ef667 --- /dev/null +++ b/src/components/NavElements/BiotopCopyingIcon.jsx @@ -0,0 +1,17 @@ +import styled from '@emotion/styled' +import { MdPhotoLibrary } from 'react-icons/md' + +import { IconContainer } from './IconContainer.jsx' + +const StyledIcon = styled(MdPhotoLibrary)` + padding-left: 0.2em; + height: 20px !important; + color: rgb(255, 90, 0) !important; + font-size: 1.5rem; +` + +export const BiotopCopyingIcon = () => ( + <IconContainer title="Biotop kopiert, bereit zum Einfügen"> + <StyledIcon /> + </IconContainer> +) diff --git a/src/components/NavElements/CopyingIcon.jsx b/src/components/NavElements/CopyingIcon.jsx new file mode 100644 index 0000000000..8d0938dcbf --- /dev/null +++ b/src/components/NavElements/CopyingIcon.jsx @@ -0,0 +1,17 @@ +import styled from '@emotion/styled' +import { MdContentCopy } from 'react-icons/md' + +import { IconContainer } from './IconContainer.jsx' + +const StyledIcon = styled(MdContentCopy)` + padding-left: 0.2em; + height: 20px !important; + color: rgb(255, 90, 0) !important; + font-size: 1.5rem; +` + +export const CopyingIcon = () => ( + <IconContainer title="kopiert, bereit zum Einfügen"> + <StyledIcon /> + </IconContainer> +) diff --git a/src/components/NavElements/IconContainer.jsx b/src/components/NavElements/IconContainer.jsx new file mode 100644 index 0000000000..f27dfbfbeb --- /dev/null +++ b/src/components/NavElements/IconContainer.jsx @@ -0,0 +1,9 @@ +import styled from '@emotion/styled' + +export const IconContainer = styled.div` + display: flex; + flex-direction: column; + align-items: center; + padding-right: 4px; + margin-left: -2px; +` diff --git a/src/components/NavElements/MapIcon.jsx b/src/components/NavElements/MapIcon.jsx new file mode 100644 index 0000000000..79c8196d0c --- /dev/null +++ b/src/components/NavElements/MapIcon.jsx @@ -0,0 +1,8 @@ +import { MdLocalFlorist } from 'react-icons/md' +import styled from '@emotion/styled' + +export const MapIcon = styled(MdLocalFlorist)` + margin-left: -2px; + height: 1.2rem !important; + font-size: 1.4rem; +` diff --git a/src/components/NavElements/MovingIcon.jsx b/src/components/NavElements/MovingIcon.jsx new file mode 100644 index 0000000000..a7538c6111 --- /dev/null +++ b/src/components/NavElements/MovingIcon.jsx @@ -0,0 +1,17 @@ +import styled from '@emotion/styled' +import { MdOutlineMoveDown } from 'react-icons/md' + +import { IconContainer } from './IconContainer.jsx' + +const StyledIcon = styled(MdOutlineMoveDown)` + padding-left: 0.2em; + height: 20px !important; + color: rgb(255, 90, 0) !important; + font-size: 1.5rem; +` + +export const MovingIcon = () => ( + <IconContainer title="zum Verschieben gemerkt, bereit um in einer anderen Art einzufügen"> + <StyledIcon /> + </IconContainer> +) diff --git a/src/components/NavElements/PopMapIcon.jsx b/src/components/NavElements/PopMapIcon.jsx new file mode 100644 index 0000000000..65388f0dd1 --- /dev/null +++ b/src/components/NavElements/PopMapIcon.jsx @@ -0,0 +1,14 @@ +import styled from '@emotion/styled' + +import { MapIcon } from './MapIcon.jsx' +import { IconContainer } from './IconContainer.jsx' + +const StyledMapIcon = styled(MapIcon)` + color: #947500 !important; +` + +export const PopMapIcon = () => ( + <IconContainer title="Populationen in Karte sichtbar"> + <StyledMapIcon /> + </IconContainer> +) diff --git a/src/components/NavElements/TpopMapIcon.jsx b/src/components/NavElements/TpopMapIcon.jsx new file mode 100644 index 0000000000..2772b3e96e --- /dev/null +++ b/src/components/NavElements/TpopMapIcon.jsx @@ -0,0 +1,14 @@ +import styled from '@emotion/styled' + +import { MapIcon } from './MapIcon.jsx' +import { IconContainer } from './IconContainer.jsx' + +const StyledMapIcon = styled(MapIcon)` + color: #016f19 !important; +` + +export const TpopMapIcon = () => ( + <IconContainer title="Teil-Populationen in Karte sichtbar"> + <StyledMapIcon /> + </IconContainer> +) diff --git a/src/components/Print/ApberForAp/index.jsx b/src/components/Print/ApberForAp/index.jsx index 911fe0baf9..4d2fb29901 100644 --- a/src/components/Print/ApberForAp/index.jsx +++ b/src/components/Print/ApberForAp/index.jsx @@ -12,11 +12,11 @@ import { Massnahmen } from './Massnahmen.jsx' import { AMengen } from './AMengen.jsx' import { BMengen } from './BMengen.jsx' import { CMengen } from './CMengen.jsx' -import { StoreContext } from '../../../storeContext.js' +import { MobxContext } from '../../../mobxContext.js' import { ErrorBoundary } from '../../shared/ErrorBoundary.jsx' -import { PopMenge } from '../../Projekte/Daten/Ap/Auswertung/PopMenge/index.jsx' -import { PopStatus } from '../../Projekte/Daten/Ap/Auswertung/PopStatus/index.jsx' -import { TpopKontrolliert } from '../../Projekte/Daten/Ap/Auswertung/TpopKontrolliert/index.jsx' +import { PopMenge } from '../../Projekte/Daten/ApAuswertung/PopMenge/index.jsx' +import { PopStatus } from '../../Projekte/Daten/ApAuswertung/PopStatus/index.jsx' +import { TpopKontrolliert } from '../../Projekte/Daten/ApAuswertung/TpopKontrolliert/index.jsx' const mdParser = new MarkdownIt({ breaks: true }) @@ -159,7 +159,7 @@ export const ApberForAp = ({ // so only when index is 0 subReportIndex, }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { setIsPrint } = store const apData = isSubReport ? apDataPassed : apDataPassed.apById diff --git a/src/components/Print/ApberForYear/index.jsx b/src/components/Print/ApberForYear/index.jsx index fd2ca0a494..d827d03cb6 100644 --- a/src/components/Print/ApberForYear/index.jsx +++ b/src/components/Print/ApberForYear/index.jsx @@ -4,7 +4,7 @@ import { useApolloClient, gql } from '@apollo/client' import { useParams } from 'react-router' import { ApberForYear } from './ApberForYear.jsx' -import { StoreContext } from '../../../storeContext.js' +import { MobxContext } from '../../../mobxContext.js' import { ErrorBoundary } from '../../shared/ErrorBoundary.jsx' import { Spinner } from '../../shared/Spinner.jsx' @@ -13,7 +13,7 @@ export const Component = () => { useParams() const client = useApolloClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { printingJberYear } = store const [year, setYear] = useState(printingJberYear) diff --git a/src/components/Projekte/ApFilterController.jsx b/src/components/Projekte/ApFilterController.jsx index 65a3dceac0..121269393b 100644 --- a/src/components/Projekte/ApFilterController.jsx +++ b/src/components/Projekte/ApFilterController.jsx @@ -3,13 +3,13 @@ import { observer } from 'mobx-react-lite' import { useParams } from 'react-router' import { useApolloClient, gql } from '@apollo/client' -import { StoreContext } from '../../storeContext.js' +import { MobxContext } from '../../mobxContext.js' export const ApFilterController = memo( observer(() => { const client = useApolloClient() const { apId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { apFilter, setApFilter } = store.tree useEffect(() => { diff --git a/src/components/Projekte/Daten/Adresse/Menu.jsx b/src/components/Projekte/Daten/Adresse/Menu.jsx index af00f61efc..423c6107ea 100644 --- a/src/components/Projekte/Daten/Adresse/Menu.jsx +++ b/src/components/Projekte/Daten/Adresse/Menu.jsx @@ -13,7 +13,7 @@ import isEqual from 'lodash/isEqual' import { MenuBar } from '../../../shared/MenuBar/index.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' const iconStyle = { color: 'white' } @@ -24,7 +24,7 @@ export const Menu = memo( const navigate = useNavigate() const client = useApolloClient() const queryClient = useQueryClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result diff --git a/src/components/Projekte/Daten/Adresse/index.jsx b/src/components/Projekte/Daten/Adresse/index.jsx index 895537c4c4..ade6f09541 100644 --- a/src/components/Projekte/Daten/Adresse/index.jsx +++ b/src/components/Projekte/Daten/Adresse/index.jsx @@ -9,7 +9,7 @@ import { Checkbox2States } from '../../../shared/Checkbox2States.jsx' import { TextField } from '../../../shared/TextField.jsx' import { FormTitle } from '../../../shared/FormTitle/index.jsx' import { query } from './query.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { Error } from '../../../shared/Error.jsx' @@ -42,7 +42,7 @@ const fieldTypes = { export const Component = memo( observer(() => { const { adrId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const queryClient = useQueryClient() const { data, error, loading } = useQuery(query, { @@ -111,7 +111,7 @@ export const Component = memo( <Container> <FormTitle title="Adresse" - menuBar={<Menu />} + MenuBarComponent={Menu} /> <FieldsContainer> <FormContainer> diff --git a/src/components/Projekte/Daten/Adresses/List.jsx b/src/components/Projekte/Daten/Adresses/List.jsx new file mode 100644 index 0000000000..49805f8b5e --- /dev/null +++ b/src/components/Projekte/Daten/Adresses/List.jsx @@ -0,0 +1,30 @@ +import { memo, useContext } from 'react' +import { observer } from 'mobx-react-lite' + +import { MobxContext } from '../../../../mobxContext.js' +import { useAdressesNavData } from '../../../../modules/useAdressesNavData.js' +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from './Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' + +export const List = memo( + observer(() => { + const store = useContext(MobxContext) + const { nodeLabelFilter } = store.tree + + const { navData, isLoading, error } = useAdressesNavData() + + if (isLoading) return <Spinner /> + + if (error) return <Error error={error} /> + + return ( + <SharedList + navData={navData} + MenuBarComponent={Menu} + highlightSearchString={nodeLabelFilter.adresse} + /> + ) + }), +) diff --git a/src/components/Projekte/Daten/Adresses/Menu.jsx b/src/components/Projekte/Daten/Adresses/Menu.jsx index 2e14728649..58476f8b53 100644 --- a/src/components/Projekte/Daten/Adresses/Menu.jsx +++ b/src/components/Projekte/Daten/Adresses/Menu.jsx @@ -10,17 +10,15 @@ import IconButton from '@mui/material/IconButton' import Tooltip from '@mui/material/Tooltip' import styled from '@emotion/styled' import { observer } from 'mobx-react-lite' -import { useAtom } from 'jotai' import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' +import { FilterButton } from '../../../shared/MenuBar/FilterButton.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' import { moveTo } from '../../../../modules/moveTo/index.js' import { copyTo } from '../../../../modules/copyTo/index.js' import { closeLowerNodes } from '../../TreeContainer/closeLowerNodes.js' -import { StoreContext } from '../../../../storeContext.js' -import { LabelFilter, labelFilterWidth } from '../../../shared/LabelFilter.jsx' -import { listLabelFilterIsIconAtom } from '../../../../JotaiStore/index.js' +import { MobxContext } from '../../../../mobxContext.js' const Fitter = styled.div` margin-top: -15px; @@ -28,13 +26,13 @@ const Fitter = styled.div` ` const iconStyle = { color: 'white' } -export const Menu = memo(() => { +export const Menu = memo(({ toggleFilterInput }) => { const { search, pathname } = useLocation() const navigate = useNavigate() const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { projId, adresseId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result @@ -68,14 +66,12 @@ export const Menu = memo(() => { navigate(`./${id}${search}`) }, [client, store, tanstackQueryClient, navigate, search]) - const [labelFilterIsIcon] = useAtom(listLabelFilterIsIconAtom) - return ( <ErrorBoundary> <MenuBar> - <LabelFilter - width={labelFilterIsIcon ? buttonWidth : labelFilterWidth} - /> + {!!toggleFilterInput && ( + <FilterButton toggleFilterInput={toggleFilterInput} /> + )} <Tooltip title="Neue Adresse erstellen"> <IconButton onClick={onClickAdd}> <FaPlus style={iconStyle} /> diff --git a/src/components/Projekte/Daten/Adresses/index.jsx b/src/components/Projekte/Daten/Adresses/index.jsx index 3695e10336..caab62a1ea 100644 --- a/src/components/Projekte/Daten/Adresses/index.jsx +++ b/src/components/Projekte/Daten/Adresses/index.jsx @@ -1,42 +1,13 @@ -import { memo, useContext } from 'react' -import { useApolloClient, gql } from '@apollo/client' -import { useQuery } from '@tanstack/react-query' -import { observer } from 'mobx-react-lite' +import { memo } from 'react' +import { useAtom } from 'jotai' -import { StoreContext } from '../../../../storeContext.js' -import { createAdressesQuery } from '../../../../modules/createAdressesQuery.js' -import { List } from '../../../shared/List/index.jsx' -import { Menu } from './Menu.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Error } from '../../../shared/Error.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { List } from './List.jsx' -export const Component = memo( - observer(() => { - const apolloClient = useApolloClient() - const store = useContext(StoreContext) - const { adresseGqlFilterForTree, nodeLabelFilter } = store.tree +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) - const { data, isLoading, error } = useQuery( - createAdressesQuery({ - adresseGqlFilterForTree, - apolloClient, - }), - ) - const adresses = data?.data?.allAdresses?.nodes ?? [] - const totalCount = data?.data?.totalCount?.totalCount ?? 0 + if (isDesktopView) return null - if (isLoading) return <Spinner /> - - if (error) return <Error error={error} /> - - return ( - <List - items={adresses} - title="Adressen" - totalCount={totalCount} - menuBar={<Menu />} - highlightSearchString={nodeLabelFilter.adresse} - /> - ) - }), -) + return <List /> +}) diff --git a/src/components/Projekte/Daten/Ap/Ap.jsx b/src/components/Projekte/Daten/Ap/Ap.jsx new file mode 100644 index 0000000000..ae01bb1811 --- /dev/null +++ b/src/components/Projekte/Daten/Ap/Ap.jsx @@ -0,0 +1,284 @@ +import { memo, useContext, useCallback, useMemo, useState } from 'react' +import styled from '@emotion/styled' +import { observer } from 'mobx-react-lite' +import { useParams } from 'react-router' +import { useQueryClient } from '@tanstack/react-query' +import { useApolloClient, useQuery, gql } from '@apollo/client' + +import { RadioButtonGroupWithInfo } from '../../../shared/RadioButtonGroupWithInfo.jsx' +import { TextField } from '../../../shared/TextField.jsx' +import { Select } from '../../../shared/Select.jsx' +import { SelectLoadingOptions } from '../../../shared/SelectLoadingOptions.jsx' +import { TextFieldNonUpdatable } from '../../../shared/TextFieldNonUpdatable.jsx' +import { queryAeTaxonomies } from './queryAeTaxonomies.js' +import { MobxContext } from '../../../../mobxContext.js' +import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' +import { ApUsers } from './ApUsers/index.jsx' +import { ap, aeTaxonomies } from '../../../shared/fragments.js' +import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' +import { query } from './query.js' +import { FormTitle } from '../../../shared/FormTitle/index.jsx' +import { Menu } from './Menu.jsx' + +const FormContainer = styled.div` + display: flex; + flex-direction: column; + flex-grow: 1; + overflow-y: auto; + scrollbar-width: thin; + padding: 10px; +` +const FieldContainer = styled.div` + display: flex; + flex-direction: column; +` +const LabelPopoverRow = styled.div` + padding: 2px 5px 2px 5px; +` +const LabelPopoverTitleRow = styled(LabelPopoverRow)` + border-top-left-radius: 4px; + border-top-right-radius: 4px; + background-color: rgb(46, 125, 50); + color: white; +` +const LabelPopoverContentRow = styled(LabelPopoverRow)` + display: flex; + border-color: grey; + border-width: thin; + border-style: solid; + border-top-style: none; + &:last-child { + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; + } +` +const LabelPopoverRowColumnLeft = styled.div` + width: 110px; +` +const LabelPopoverRowColumnRight = styled.div` + padding-left: 5px; +` + +const fieldTypes = { + bearbeitung: 'Int', + startJahr: 'Int', + umsetzung: 'Int', + artId: 'UUID', + bearbeiter: 'UUID', + ekfBeobachtungszeitpunkt: 'String', + projId: 'UUID', +} + +export const Component = memo( + observer(() => { + const { apId } = useParams() + + const client = useApolloClient() + const store = useContext(MobxContext) + const { user } = store + const queryClient = useQueryClient() + + const [fieldErrors, setFieldErrors] = useState({}) + + const { data, error, loading } = useQuery(query, { + variables: { id: apId }, + }) + + const row = useMemo(() => data?.apById ?? {}, [data?.apById]) + + const saveToDb = useCallback( + async (event) => { + const field = event.target.name + const value = ifIsNumericAsNumber(event.target.value) + + const variables = { + id: row.id, + [field]: value, + changedBy: user.name, + } + try { + await client.mutate({ + mutation: gql` + mutation updateAp( + $id: UUID! + $${field}: ${fieldTypes[field]} + $changedBy: String + ) { + updateApById( + input: { + id: $id + apPatch: { + ${field}: $${field} + changedBy: $changedBy + } + } + ) { + ap { + ...ApFields + aeTaxonomyByArtId { + ...AeTaxonomiesFields + } + } + } + } + ${ap} + ${aeTaxonomies} + `, + variables, + }) + } catch (error) { + return setFieldErrors({ [field]: error.message }) + } + setFieldErrors({}) + if (field === 'artId') { + queryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) + } + }, + [client, queryClient, row.id, user.name], + ) + + const aeTaxonomiesfilterForData = useCallback( + (inputValue) => + inputValue ? + { + or: [ + { apByArtIdExists: false }, + { apByArtId: { id: { equalTo: apId } } }, + ], + taxArtName: { includesInsensitive: inputValue }, + } + : { + or: [ + { apByArtIdExists: false }, + { apByArtId: { id: { equalTo: apId } } }, + ], + }, + [apId], + ) + + return ( + <ErrorBoundary> + <FormTitle + title="Art" + MenuBarComponent={Menu} + /> + <FormContainer> + <SelectLoadingOptions + field="artId" + valueLabelPath="aeTaxonomyByArtId.taxArtName" + label="Art (das namensgebende Taxon)" + row={row} + query={queryAeTaxonomies} + filter={aeTaxonomiesfilterForData} + queryNodesName="allAeTaxonomies" + //value={row.artId} + saveToDb={saveToDb} + error={fieldErrors.artId} + /> + <RadioButtonGroupWithInfo + name="bearbeitung" + dataSource={data?.allApBearbstandWertes?.nodes ?? []} + loading={false} + popover={ + <> + <LabelPopoverTitleRow data-id="info-icon-popover"> + Legende + </LabelPopoverTitleRow> + <LabelPopoverContentRow> + <LabelPopoverRowColumnLeft>keiner:</LabelPopoverRowColumnLeft> + <LabelPopoverRowColumnRight> + kein Aktionsplan vorgesehen + </LabelPopoverRowColumnRight> + </LabelPopoverContentRow> + <LabelPopoverContentRow> + <LabelPopoverRowColumnLeft> + erstellt: + </LabelPopoverRowColumnLeft> + <LabelPopoverRowColumnRight> + Aktionsplan fertig, auf der Webseite der FNS + </LabelPopoverRowColumnRight> + </LabelPopoverContentRow> + </> + } + label="Aktionsplan" + value={row.bearbeitung} + saveToDb={saveToDb} + error={fieldErrors.bearbeitung} + /> + <TextField + name="startJahr" + label="Start im Jahr" + type="number" + value={row.startJahr} + saveToDb={saveToDb} + error={fieldErrors.startJahr} + /> + <FieldContainer> + <RadioButtonGroupWithInfo + name="umsetzung" + dataSource={data?.allApUmsetzungWertes?.nodes ?? []} + loading={false} + popover={ + <> + <LabelPopoverTitleRow data-id="info-icon-popover"> + Legende + </LabelPopoverTitleRow> + <LabelPopoverContentRow> + <LabelPopoverRowColumnLeft> + noch keine + <br /> + Umsetzung: + </LabelPopoverRowColumnLeft> + <LabelPopoverRowColumnRight> + noch keine Massnahmen ausgeführt + </LabelPopoverRowColumnRight> + </LabelPopoverContentRow> + <LabelPopoverContentRow> + <LabelPopoverRowColumnLeft> + in Umsetzung: + </LabelPopoverRowColumnLeft> + <LabelPopoverRowColumnRight> + bereits Massnahmen ausgeführt (auch wenn AP noch nicht + erstellt) + </LabelPopoverRowColumnRight> + </LabelPopoverContentRow> + </> + } + label="Stand Umsetzung" + value={row.umsetzung} + saveToDb={saveToDb} + error={fieldErrors.umsetzung} + /> + </FieldContainer> + <Select + name="bearbeiter" + label="Verantwortlich" + options={data?.allAdresses?.nodes ?? []} + loading={false} + value={row.bearbeiter} + saveToDb={saveToDb} + error={fieldErrors.bearbeiter} + /> + <ApUsers /> + <TextField + name="ekfBeobachtungszeitpunkt" + label="Bester Beobachtungszeitpunkt für EKF (Freiwilligen-Kontrollen)" + type="text" + value={row.ekfBeobachtungszeitpunkt} + saveToDb={saveToDb} + error={fieldErrors.ekfBeobachtungszeitpunkt} + /> + <TextFieldNonUpdatable + key={`${row.id}artwert`} + label="Artwert" + value={ + row?.aeTaxonomyByArtId?.artwert ?? 'Diese Art hat keinen Artwert' + } + /> + </FormContainer> + </ErrorBoundary> + ) + }), +) diff --git a/src/components/Projekte/Daten/Ap/ApUsers/ApUser.jsx b/src/components/Projekte/Daten/Ap/ApUsers/ApUser.jsx index e372cfab19..df5111a68e 100644 --- a/src/components/Projekte/Daten/Ap/ApUsers/ApUser.jsx +++ b/src/components/Projekte/Daten/Ap/ApUsers/ApUser.jsx @@ -6,7 +6,7 @@ import Tooltip from '@mui/material/Tooltip' import styled from '@emotion/styled' import { useApolloClient, gql } from '@apollo/client' -import { StoreContext } from '../../../../../storeContext.js' +import { MobxContext } from '../../../../../mobxContext.js' const DelIcon = styled(IconButton)` font-size: 1rem !important; @@ -17,7 +17,7 @@ const DelIcon = styled(IconButton)` export const ApUser = memo( observer(({ user, refetch }) => { const client = useApolloClient() - const { enqueNotification } = useContext(StoreContext) + const { enqueNotification } = useContext(MobxContext) const onClickDelete = useCallback(async () => { try { diff --git a/src/components/Projekte/Daten/Ap/Auswertung/index.jsx b/src/components/Projekte/Daten/Ap/Auswertung/index.jsx deleted file mode 100644 index cd21a78d6b..0000000000 --- a/src/components/Projekte/Daten/Ap/Auswertung/index.jsx +++ /dev/null @@ -1,27 +0,0 @@ -import { memo } from 'react' -import styled from '@emotion/styled' - -import { ApErfolg } from './ApErfolg/index.jsx' -import { PopStatus } from './PopStatus/index.jsx' -import { PopMenge } from './PopMenge/index.jsx' -import { TpopKontrolliert } from './TpopKontrolliert/index.jsx' - -const FormContainer = styled.div` - display: flex; - flex-direction: column; - flex-grow: 1; - overflow: hidden; - overflow-y: auto; - scrollbar-width: thin; - padding: 10px; - padding-top: 0; -` - -export const Component = memo(() => ( - <FormContainer> - <ApErfolg /> - <PopStatus /> - <PopMenge /> - <TpopKontrolliert /> - </FormContainer> -)) diff --git a/src/components/Projekte/Daten/Ap/Dateien.jsx b/src/components/Projekte/Daten/Ap/Dateien.jsx index 386063fcb3..9e1379dfc3 100644 --- a/src/components/Projekte/Daten/Ap/Dateien.jsx +++ b/src/components/Projekte/Daten/Ap/Dateien.jsx @@ -1,15 +1,37 @@ import { memo } from 'react' import { useParams } from 'react-router' +import { useQuery, gql } from '@apollo/client' import { FilesRouter } from '../../../shared/Files/index.jsx' +import { FormTitle } from '../../../shared/FormTitle/index.jsx' + +const apFilesQuery = gql` + query apFilesQuery($apId: UUID!) { + apById(id: $apId) { + id + aeTaxonomyByArtId { + id + artname + } + } + } +` export const Component = memo(() => { const { apId } = useParams() + const { data } = useQuery(apFilesQuery, { + variables: { apId }, + }) + + const artname = data?.apById?.aeTaxonomyByArtId?.artname ?? 'Art' return ( - <FilesRouter - parentId={apId} - parent="ap" - /> + <> + <FormTitle title={`${artname}: Dateien`} /> + <FilesRouter + parentId={apId} + parent="ap" + /> + </> ) }) diff --git a/src/components/Projekte/Daten/Ap/Historien.jsx b/src/components/Projekte/Daten/Ap/Historien.jsx index 09fb00c242..f63be5abd9 100644 --- a/src/components/Projekte/Daten/Ap/Historien.jsx +++ b/src/components/Projekte/Daten/Ap/Historien.jsx @@ -6,6 +6,7 @@ import { useParams } from 'react-router' import { Spinner } from '../../../shared/Spinner.jsx' import { History as HistoryComponent } from '../../../shared/History/index.jsx' import { appBaseUrl } from '../../../../modules/appBaseUrl.js' +import { FormTitle } from '../../../shared/FormTitle/index.jsx' const apHistoriesQuery = gql` query apHistoryQuery($apId: UUID!) { @@ -76,7 +77,7 @@ const apHistoriesQuery = gql` ` const InnerContainer = styled.div` - padding: 8px 25px 0 25px; + padding: 10px; height: 100%; overflow: hidden; overflow-y: auto; @@ -105,6 +106,9 @@ const DocLink = styled.span` ` const DocLine = styled.p` margin-bottom: 0; + &:first-of-type { + margin-top: 0; + } ` const Aenderung = styled.span` background-color: rgba(216, 67, 21, 0.2); @@ -124,6 +128,7 @@ export const Component = () => { const row = data?.apById const rows = data?.allApHistories.nodes ?? [] + const artname = row?.aeTaxonomyByArtId?.artname ?? 'Art' const openDocs = useCallback(() => { const url = `${appBaseUrl()}/Dokumentation/historisierung` @@ -142,61 +147,64 @@ export const Component = () => { } return ( - <InnerContainer> - <DocLine> - Jährlich historisierte Daten der Art ( - <DocLink onClick={openDocs}>Dokumentation</DocLink> - ). - </DocLine> - <DocLine> - <Aenderung>Änderungen</Aenderung> zum{' '} - <Aktuell>aktuellen Zustand</Aktuell> sind hervorgehoben. - </DocLine> - {rows.map((r) => { - const dataArray = [ - { - valueInRow: row?.aeTaxonomyByArtId?.artname ?? row?.artId, - valueInHist: r?.aeTaxonomyByArtId?.artname ?? r?.artId, - label: 'Art (id)', - }, - { - valueInRow: - row?.apBearbstandWerteByBearbeitung?.text ?? row?.bearbeitung, - valueInHist: - r?.apBearbstandWerteByBearbeitung?.text ?? r?.bearbeitung, - label: 'Aktionsplan', - }, - { - valueInRow: - row?.apUmsetzungWerteByUmsetzung?.text ?? row?.umsetzung, - valueInHist: r?.apUmsetzungWerteByUmsetzung?.text ?? r?.umsetzung, - label: 'Stand Umsetzung', - }, - { - valueInRow: row?.adresseByBearbeiter?.name ?? row?.bearbeiter, - valueInHist: r?.adresseByBearbeiter?.name ?? r?.bearbeiter, - label: 'Verantwortlich', - }, - { - valueInRow: row?.startJahr, - valueInHist: r?.startJahr, - label: 'Start im Jahr', - }, - { - valueInRow: row?.ekfBeobachtungszeitpunkt, - valueInHist: r?.ekfBeobachtungszeitpunkt, - label: 'Bester Beobachtungszeitpunkt für EKF', - }, - ] + <> + <FormTitle title={`${artname}: Historien`} /> + <InnerContainer> + <DocLine> + Jährlich historisierte Daten der Art ( + <DocLink onClick={openDocs}>Dokumentation</DocLink> + ). + </DocLine> + <DocLine> + <Aenderung>Änderungen</Aenderung> zum{' '} + <Aktuell>aktuellen Zustand</Aktuell> sind hervorgehoben. + </DocLine> + {rows.map((r) => { + const dataArray = [ + { + valueInRow: row?.aeTaxonomyByArtId?.artname ?? row?.artId, + valueInHist: r?.aeTaxonomyByArtId?.artname ?? r?.artId, + label: 'Art (id)', + }, + { + valueInRow: + row?.apBearbstandWerteByBearbeitung?.text ?? row?.bearbeitung, + valueInHist: + r?.apBearbstandWerteByBearbeitung?.text ?? r?.bearbeitung, + label: 'Aktionsplan', + }, + { + valueInRow: + row?.apUmsetzungWerteByUmsetzung?.text ?? row?.umsetzung, + valueInHist: r?.apUmsetzungWerteByUmsetzung?.text ?? r?.umsetzung, + label: 'Stand Umsetzung', + }, + { + valueInRow: row?.adresseByBearbeiter?.name ?? row?.bearbeiter, + valueInHist: r?.adresseByBearbeiter?.name ?? r?.bearbeiter, + label: 'Verantwortlich', + }, + { + valueInRow: row?.startJahr, + valueInHist: r?.startJahr, + label: 'Start im Jahr', + }, + { + valueInRow: row?.ekfBeobachtungszeitpunkt, + valueInHist: r?.ekfBeobachtungszeitpunkt, + label: 'Bester Beobachtungszeitpunkt für EKF', + }, + ] - return ( - <HistoryComponent - key={`${r.id}${r.year}`} - year={r?.year} - dataArray={dataArray} - /> - ) - })} - </InnerContainer> + return ( + <HistoryComponent + key={`${r.id}${r.year}`} + year={r?.year} + dataArray={dataArray} + /> + ) + })} + </InnerContainer> + </> ) } diff --git a/src/components/Projekte/Daten/Ap/List.jsx b/src/components/Projekte/Daten/Ap/List.jsx new file mode 100644 index 0000000000..8eeb62f631 --- /dev/null +++ b/src/components/Projekte/Daten/Ap/List.jsx @@ -0,0 +1,22 @@ +import { memo } from 'react' + +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from './Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' +import { useApNavData } from '../../../../modules/useApNavData.js' + +export const List = memo(() => { + const { navData, isLoading, error } = useApNavData() + + if (isLoading) return <Spinner /> + + if (error) return <Error error={error} /> + + return ( + <SharedList + navData={navData} + MenuBarComponent={Menu} + /> + ) +}) diff --git a/src/components/Projekte/Daten/ApRouter/Menu.jsx b/src/components/Projekte/Daten/Ap/Menu.jsx similarity index 97% rename from src/components/Projekte/Daten/ApRouter/Menu.jsx rename to src/components/Projekte/Daten/Ap/Menu.jsx index 98d5d5654d..af6e309a63 100644 --- a/src/components/Projekte/Daten/ApRouter/Menu.jsx +++ b/src/components/Projekte/Daten/Ap/Menu.jsx @@ -16,7 +16,7 @@ import isEqual from 'lodash/isEqual' import { MenuBar } from '../../../shared/MenuBar/index.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' import { moveTo } from '../../../../modules/moveTo/index.js' import { copyTo } from '../../../../modules/copyTo/index.js' @@ -31,7 +31,7 @@ export const Menu = memo( const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { projId, apId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { setMoving, moving, copying, setCopying } = store @@ -66,7 +66,7 @@ export const Menu = memo( queryKey: [`treeRoot`], }) const id = result?.data?.createAp?.ap?.id - navigate(`/Daten/Projekte/${projId}/Arten/${id}${search}`) + navigate(`/Daten/Projekte/${projId}/Arten/${id}/Art${search}`) }, [projId, client, store, tanstackQueryClient, navigate, search]) const [delMenuAnchorEl, setDelMenuAnchorEl] = useState(null) diff --git a/src/components/Projekte/Daten/Ap/index.jsx b/src/components/Projekte/Daten/Ap/index.jsx index 1e9e3c11a7..b7d4f1ced8 100644 --- a/src/components/Projekte/Daten/Ap/index.jsx +++ b/src/components/Projekte/Daten/Ap/index.jsx @@ -1,274 +1,14 @@ -import { memo, useContext, useCallback, useMemo, useState } from 'react' -import styled from '@emotion/styled' -import { observer } from 'mobx-react-lite' -import { useApolloClient, gql } from '@apollo/client' -import { useParams, useOutletContext } from 'react-router' -import { useQueryClient } from '@tanstack/react-query' +import { memo } from 'react' +import { useAtom } from 'jotai' -import { RadioButtonGroupWithInfo } from '../../../shared/RadioButtonGroupWithInfo.jsx' -import { TextField } from '../../../shared/TextField.jsx' -import { Select } from '../../../shared/Select.jsx' -import { SelectLoadingOptions } from '../../../shared/SelectLoadingOptions.jsx' -import { TextFieldNonUpdatable } from '../../../shared/TextFieldNonUpdatable.jsx' -import { queryAeTaxonomies } from './queryAeTaxonomies.js' -import { StoreContext } from '../../../../storeContext.js' -import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' -import { ApUsers } from './ApUsers/index.jsx' -import { ap, aeTaxonomies } from '../../../shared/fragments.js' -import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Ap } from './Ap.jsx' +import { List } from './List.jsx' -const FormContainer = styled.div` - display: flex; - flex-direction: column; - flex-grow: 1; - overflow-y: auto; - scrollbar-width: thin; - padding: 10px; -` -const FieldContainer = styled.div` - display: flex; - flex-direction: column; -` -const LabelPopoverRow = styled.div` - padding: 2px 5px 2px 5px; -` -const LabelPopoverTitleRow = styled(LabelPopoverRow)` - border-top-left-radius: 4px; - border-top-right-radius: 4px; - background-color: rgb(46, 125, 50); - color: white; -` -const LabelPopoverContentRow = styled(LabelPopoverRow)` - display: flex; - border-color: grey; - border-width: thin; - border-style: solid; - border-top-style: none; - &:last-child { - border-bottom-right-radius: 4px; - border-bottom-left-radius: 4px; - } -` -const LabelPopoverRowColumnLeft = styled.div` - width: 110px; -` -const LabelPopoverRowColumnRight = styled.div` - padding-left: 5px; -` +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) -const fieldTypes = { - bearbeitung: 'Int', - startJahr: 'Int', - umsetzung: 'Int', - artId: 'UUID', - bearbeiter: 'UUID', - ekfBeobachtungszeitpunkt: 'String', - projId: 'UUID', -} + if (isDesktopView) return <Ap /> -export const Component = memo( - observer(() => { - const { apId } = useParams() - const { data } = useOutletContext() - - const client = useApolloClient() - const store = useContext(StoreContext) - const { user } = store - const queryClient = useQueryClient() - - const [fieldErrors, setFieldErrors] = useState({}) - - const row = useMemo(() => data?.apById ?? {}, [data?.apById]) - - const saveToDb = useCallback( - async (event) => { - const field = event.target.name - const value = ifIsNumericAsNumber(event.target.value) - - const variables = { - id: row.id, - [field]: value, - changedBy: user.name, - } - try { - await client.mutate({ - mutation: gql` - mutation updateAp( - $id: UUID! - $${field}: ${fieldTypes[field]} - $changedBy: String - ) { - updateApById( - input: { - id: $id - apPatch: { - ${field}: $${field} - changedBy: $changedBy - } - } - ) { - ap { - ...ApFields - aeTaxonomyByArtId { - ...AeTaxonomiesFields - } - } - } - } - ${ap} - ${aeTaxonomies} - `, - variables, - }) - } catch (error) { - return setFieldErrors({ [field]: error.message }) - } - setFieldErrors({}) - if (field === 'artId') { - queryClient.invalidateQueries({ - queryKey: [`treeAp`], - }) - } - }, - [client, queryClient, row.id, user.name], - ) - - const aeTaxonomiesfilterForData = useCallback( - (inputValue) => - inputValue ? - { - or: [ - { apByArtIdExists: false }, - { apByArtId: { id: { equalTo: apId } } }, - ], - taxArtName: { includesInsensitive: inputValue }, - } - : { - or: [ - { apByArtIdExists: false }, - { apByArtId: { id: { equalTo: apId } } }, - ], - }, - [apId], - ) - - return ( - <ErrorBoundary> - <FormContainer> - <SelectLoadingOptions - field="artId" - valueLabelPath="aeTaxonomyByArtId.taxArtName" - label="Art (das namensgebende Taxon)" - row={row} - query={queryAeTaxonomies} - filter={aeTaxonomiesfilterForData} - queryNodesName="allAeTaxonomies" - //value={row.artId} - saveToDb={saveToDb} - error={fieldErrors.artId} - /> - <RadioButtonGroupWithInfo - name="bearbeitung" - dataSource={data?.allApBearbstandWertes?.nodes ?? []} - loading={false} - popover={ - <> - <LabelPopoverTitleRow data-id="info-icon-popover"> - Legende - </LabelPopoverTitleRow> - <LabelPopoverContentRow> - <LabelPopoverRowColumnLeft>keiner:</LabelPopoverRowColumnLeft> - <LabelPopoverRowColumnRight> - kein Aktionsplan vorgesehen - </LabelPopoverRowColumnRight> - </LabelPopoverContentRow> - <LabelPopoverContentRow> - <LabelPopoverRowColumnLeft> - erstellt: - </LabelPopoverRowColumnLeft> - <LabelPopoverRowColumnRight> - Aktionsplan fertig, auf der Webseite der FNS - </LabelPopoverRowColumnRight> - </LabelPopoverContentRow> - </> - } - label="Aktionsplan" - value={row.bearbeitung} - saveToDb={saveToDb} - error={fieldErrors.bearbeitung} - /> - <TextField - name="startJahr" - label="Start im Jahr" - type="number" - value={row.startJahr} - saveToDb={saveToDb} - error={fieldErrors.startJahr} - /> - <FieldContainer> - <RadioButtonGroupWithInfo - name="umsetzung" - dataSource={data?.allApUmsetzungWertes?.nodes ?? []} - loading={false} - popover={ - <> - <LabelPopoverTitleRow data-id="info-icon-popover"> - Legende - </LabelPopoverTitleRow> - <LabelPopoverContentRow> - <LabelPopoverRowColumnLeft> - noch keine - <br /> - Umsetzung: - </LabelPopoverRowColumnLeft> - <LabelPopoverRowColumnRight> - noch keine Massnahmen ausgeführt - </LabelPopoverRowColumnRight> - </LabelPopoverContentRow> - <LabelPopoverContentRow> - <LabelPopoverRowColumnLeft> - in Umsetzung: - </LabelPopoverRowColumnLeft> - <LabelPopoverRowColumnRight> - bereits Massnahmen ausgeführt (auch wenn AP noch nicht - erstellt) - </LabelPopoverRowColumnRight> - </LabelPopoverContentRow> - </> - } - label="Stand Umsetzung" - value={row.umsetzung} - saveToDb={saveToDb} - error={fieldErrors.umsetzung} - /> - </FieldContainer> - <Select - name="bearbeiter" - label="Verantwortlich" - options={data?.allAdresses?.nodes ?? []} - loading={false} - value={row.bearbeiter} - saveToDb={saveToDb} - error={fieldErrors.bearbeiter} - /> - <ApUsers /> - <TextField - name="ekfBeobachtungszeitpunkt" - label="Bester Beobachtungszeitpunkt für EKF (Freiwilligen-Kontrollen)" - type="text" - value={row.ekfBeobachtungszeitpunkt} - saveToDb={saveToDb} - error={fieldErrors.ekfBeobachtungszeitpunkt} - /> - <TextFieldNonUpdatable - key={`${row.id}artwert`} - label="Artwert" - value={ - row?.aeTaxonomyByArtId?.artwert ?? 'Diese Art hat keinen Artwert' - } - /> - </FormContainer> - </ErrorBoundary> - ) - }), -) + return <List /> +}) diff --git a/src/components/Projekte/Daten/ApRouter/query.js b/src/components/Projekte/Daten/Ap/query.js similarity index 100% rename from src/components/Projekte/Daten/ApRouter/query.js rename to src/components/Projekte/Daten/Ap/query.js diff --git a/src/components/Projekte/Daten/Ap/Auswertung/ApErfolg/CustomTick.jsx b/src/components/Projekte/Daten/ApAuswertung/ApErfolg/CustomTick.jsx similarity index 100% rename from src/components/Projekte/Daten/Ap/Auswertung/ApErfolg/CustomTick.jsx rename to src/components/Projekte/Daten/ApAuswertung/ApErfolg/CustomTick.jsx diff --git a/src/components/Projekte/Daten/Ap/Auswertung/ApErfolg/index.jsx b/src/components/Projekte/Daten/ApAuswertung/ApErfolg/index.jsx similarity index 98% rename from src/components/Projekte/Daten/Ap/Auswertung/ApErfolg/index.jsx rename to src/components/Projekte/Daten/ApAuswertung/ApErfolg/index.jsx index fda90052ba..c913d46f50 100644 --- a/src/components/Projekte/Daten/Ap/Auswertung/ApErfolg/index.jsx +++ b/src/components/Projekte/Daten/ApAuswertung/ApErfolg/index.jsx @@ -17,7 +17,7 @@ import { useParams } from 'react-router' import { query } from './query.js' import { CustomTick } from './CustomTick.jsx' -import { Error } from '../../../../../shared/Error.jsx' +import { Error } from '../../../../shared/Error.jsx' const SpinnerContainer = styled.div` height: 400px; diff --git a/src/components/Projekte/Daten/Ap/Auswertung/ApErfolg/query.js b/src/components/Projekte/Daten/ApAuswertung/ApErfolg/query.js similarity index 100% rename from src/components/Projekte/Daten/Ap/Auswertung/ApErfolg/query.js rename to src/components/Projekte/Daten/ApAuswertung/ApErfolg/query.js diff --git a/src/components/Projekte/Daten/Ap/Auswertung/CustomTooltip.jsx b/src/components/Projekte/Daten/ApAuswertung/CustomTooltip.jsx similarity index 95% rename from src/components/Projekte/Daten/Ap/Auswertung/CustomTooltip.jsx rename to src/components/Projekte/Daten/ApAuswertung/CustomTooltip.jsx index a7912a4c60..eaed70500b 100644 --- a/src/components/Projekte/Daten/Ap/Auswertung/CustomTooltip.jsx +++ b/src/components/Projekte/Daten/ApAuswertung/CustomTooltip.jsx @@ -1,7 +1,7 @@ import { memo } from 'react' import styled from '@emotion/styled' -import { exists } from '../../../../../modules/exists.js' +import { exists } from '../../../../modules/exists.js' const Popup = styled.div` background-color: white; diff --git a/src/components/Projekte/Daten/Ap/Auswertung/PopMenge/CustomTooltip.jsx b/src/components/Projekte/Daten/ApAuswertung/PopMenge/CustomTooltip.jsx similarity index 96% rename from src/components/Projekte/Daten/Ap/Auswertung/PopMenge/CustomTooltip.jsx rename to src/components/Projekte/Daten/ApAuswertung/PopMenge/CustomTooltip.jsx index 0bbf5a65d8..806ea62481 100644 --- a/src/components/Projekte/Daten/Ap/Auswertung/PopMenge/CustomTooltip.jsx +++ b/src/components/Projekte/Daten/ApAuswertung/PopMenge/CustomTooltip.jsx @@ -2,7 +2,7 @@ import { memo } from 'react' import styled from '@emotion/styled' import sortBy from 'lodash/sortBy' -import { exists } from '../../../../../../modules/exists.js' +import { exists } from '../../../../../modules/exists.js' const Popup = styled.div` background-color: white; diff --git a/src/components/Projekte/Daten/Ap/Auswertung/PopMenge/index.jsx b/src/components/Projekte/Daten/ApAuswertung/PopMenge/index.jsx similarity index 98% rename from src/components/Projekte/Daten/Ap/Auswertung/PopMenge/index.jsx rename to src/components/Projekte/Daten/ApAuswertung/PopMenge/index.jsx index 2c265172d5..fca8a3f4bf 100644 --- a/src/components/Projekte/Daten/Ap/Auswertung/PopMenge/index.jsx +++ b/src/components/Projekte/Daten/ApAuswertung/PopMenge/index.jsx @@ -19,8 +19,8 @@ import { useParams } from 'react-router' import { query } from './query.js' import { CustomTooltip } from './CustomTooltip.jsx' -import { exists } from '../../../../../../modules/exists.js' -import { Error } from '../../../../../shared/Error.jsx' +import { exists } from '../../../../../modules/exists.js' +import { Error } from '../../../../shared/Error.jsx' const SpinnerContainer = styled.div` height: 400px; diff --git a/src/components/Projekte/Daten/Ap/Auswertung/PopMenge/query.js b/src/components/Projekte/Daten/ApAuswertung/PopMenge/query.js similarity index 100% rename from src/components/Projekte/Daten/Ap/Auswertung/PopMenge/query.js rename to src/components/Projekte/Daten/ApAuswertung/PopMenge/query.js diff --git a/src/components/Projekte/Daten/Ap/Auswertung/PopStatus/index.jsx b/src/components/Projekte/Daten/ApAuswertung/PopStatus/index.jsx similarity index 99% rename from src/components/Projekte/Daten/Ap/Auswertung/PopStatus/index.jsx rename to src/components/Projekte/Daten/ApAuswertung/PopStatus/index.jsx index 3e06008f38..615030f273 100644 --- a/src/components/Projekte/Daten/Ap/Auswertung/PopStatus/index.jsx +++ b/src/components/Projekte/Daten/ApAuswertung/PopStatus/index.jsx @@ -16,7 +16,7 @@ import { useParams } from 'react-router' import { query } from './query.js' import { CustomTooltip } from '../CustomTooltip.jsx' -import { Error } from '../../../../../shared/Error.jsx' +import { Error } from '../../../../shared/Error.jsx' const SpinnerContainer = styled.div` height: 400px; diff --git a/src/components/Projekte/Daten/Ap/Auswertung/PopStatus/query.js b/src/components/Projekte/Daten/ApAuswertung/PopStatus/query.js similarity index 100% rename from src/components/Projekte/Daten/Ap/Auswertung/PopStatus/query.js rename to src/components/Projekte/Daten/ApAuswertung/PopStatus/query.js diff --git a/src/components/Projekte/Daten/Ap/Auswertung/TpopKontrolliert/index.jsx b/src/components/Projekte/Daten/ApAuswertung/TpopKontrolliert/index.jsx similarity index 98% rename from src/components/Projekte/Daten/Ap/Auswertung/TpopKontrolliert/index.jsx rename to src/components/Projekte/Daten/ApAuswertung/TpopKontrolliert/index.jsx index 8988524837..28f7415022 100644 --- a/src/components/Projekte/Daten/Ap/Auswertung/TpopKontrolliert/index.jsx +++ b/src/components/Projekte/Daten/ApAuswertung/TpopKontrolliert/index.jsx @@ -16,7 +16,7 @@ import { useParams } from 'react-router' import { query } from './query.js' import { CustomTooltip } from '../CustomTooltip.jsx' -import { Error } from '../../../../../shared/Error.jsx' +import { Error } from '../../../../shared/Error.jsx' const SpinnerContainer = styled.div` height: 400px; diff --git a/src/components/Projekte/Daten/Ap/Auswertung/TpopKontrolliert/query.js b/src/components/Projekte/Daten/ApAuswertung/TpopKontrolliert/query.js similarity index 100% rename from src/components/Projekte/Daten/Ap/Auswertung/TpopKontrolliert/query.js rename to src/components/Projekte/Daten/ApAuswertung/TpopKontrolliert/query.js diff --git a/src/components/Projekte/Daten/ApAuswertung/index.jsx b/src/components/Projekte/Daten/ApAuswertung/index.jsx new file mode 100644 index 0000000000..8ba626364b --- /dev/null +++ b/src/components/Projekte/Daten/ApAuswertung/index.jsx @@ -0,0 +1,54 @@ +import { memo } from 'react' +import styled from '@emotion/styled' +import { useParams } from 'react-router' +import { useQuery, gql } from '@apollo/client' + +import { ApErfolg } from './ApErfolg/index.jsx' +import { PopStatus } from './PopStatus/index.jsx' +import { PopMenge } from './PopMenge/index.jsx' +import { TpopKontrolliert } from './TpopKontrolliert/index.jsx' +import { FormTitle } from '../../../shared/FormTitle/index.jsx' + +const apAuswertungQuery = gql` + query apAuswertungQuery($apId: UUID!) { + apById(id: $apId) { + id + aeTaxonomyByArtId { + id + artname + } + } + } +` + +const FormContainer = styled.div` + display: flex; + flex-direction: column; + flex-grow: 1; + overflow: hidden; + overflow-y: auto; + scrollbar-width: thin; + padding: 10px; + padding-top: 0; +` + +export const Component = memo(() => { + const { apId } = useParams() + const { data } = useQuery(apAuswertungQuery, { + variables: { apId }, + }) + + const artname = data?.apById?.aeTaxonomyByArtId?.artname ?? 'Art' + + return ( + <> + <FormTitle title={`${artname}: Auswertung`} /> + <FormContainer> + <ApErfolg /> + <PopStatus /> + <PopMenge /> + <TpopKontrolliert /> + </FormContainer> + </> + ) +}) diff --git a/src/components/Projekte/Daten/ApFilter/Tabs.jsx b/src/components/Projekte/Daten/ApFilter/Tabs.jsx index cbf0153d85..173db1d108 100644 --- a/src/components/Projekte/Daten/ApFilter/Tabs.jsx +++ b/src/components/Projekte/Daten/ApFilter/Tabs.jsx @@ -4,7 +4,7 @@ import Tab from '@mui/material/Tab' import styled from '@emotion/styled' import { initial as ap } from '../../../../store/Tree/DataFilter/ap' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' const Row = styled.div`` const Title = styled.div` @@ -24,7 +24,7 @@ const StyledTab = styled(Tab)` ` export const Tabs = ({ activeTab, setActiveTab, dataFilter }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { dataFilterAddOr } = store.tree const lastFilterIsEmpty = diff --git a/src/components/Projekte/Daten/ApFilter/index.jsx b/src/components/Projekte/Daten/ApFilter/index.jsx index bfb4f46854..7f71fc4bb2 100644 --- a/src/components/Projekte/Daten/ApFilter/index.jsx +++ b/src/components/Projekte/Daten/ApFilter/index.jsx @@ -13,7 +13,7 @@ import { queryLists } from './queryLists.js' import { queryAps } from './queryAps.js' import { queryAdresses } from './queryAdresses.js' import { queryAeTaxonomies } from './queryAeTaxonomies.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { Error } from '../../../shared/Error.jsx' @@ -83,7 +83,7 @@ const FilterComment = styled.li` export const ApFilter = memo( observer(() => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { dataFilter, apFilter: nurApFilter, diff --git a/src/components/Projekte/Daten/ApRouter/index.jsx b/src/components/Projekte/Daten/ApRouter/index.jsx deleted file mode 100644 index d82c638ed5..0000000000 --- a/src/components/Projekte/Daten/ApRouter/index.jsx +++ /dev/null @@ -1,115 +0,0 @@ -import { useCallback, Suspense, useMemo } from 'react' -import styled from '@emotion/styled' -import Tabs from '@mui/material/Tabs' -import Tab from '@mui/material/Tab' -import { useParams, Outlet, useNavigate, useLocation } from 'react-router' -import { useApolloClient, useQuery, gql } from '@apollo/client' - -import { FormTitle } from '../../../shared/FormTitle/index.jsx' -import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Error } from '../../../shared/Error.jsx' -import { query } from './query.js' -import { Menu } from '../ApRouter/Menu.jsx' - -const Container = styled.div` - flex-grow: 1; - display: flex; - flex-direction: column; - overflow: hidden; -` -const StyledTab = styled(Tab)` - text-transform: none !important; -` -const TabContentContainer = styled.div` - flex-grow: 1; - display: flex; - flex-direction: column; - overflow: hidden; -` -const TabContent = styled.div` - flex-grow: 1; - display: flex; - flex-direction: column; - overflow: hidden; -` - -export const Component = () => { - const { apId } = useParams() - const navigate = useNavigate() - const { pathname, search } = useLocation() - - const { data, error, loading } = useQuery(query, { - variables: { id: apId }, - }) - - const row = useMemo(() => data?.apById ?? {}, [data?.apById]) - - const onChangeTab = useCallback( - (event, value) => - pathname.endsWith(apId) ? - navigate(`${value}${search}`) - : navigate(`../${value}${search}`), - [pathname, apId, navigate, search], - ) - - return ( - <ErrorBoundary> - <Container> - <FormTitle - title="Art" - menuBar={<Menu />} - /> - <Tabs - value={ - pathname.includes(`${apId}/Art`) ? 'Art' - : pathname.includes(`${apId}/Auswertung`) ? - 'Auswertung' - : pathname.includes(`${apId}/Dateien`) ? - 'Dateien' - : pathname.includes(`${apId}/Historien`) ? - 'Historien' - : 'Art' - } - onChange={onChangeTab} - indicatorColor="primary" - textColor="primary" - centered - > - <StyledTab - label="Art" - value="Art" - data-id="Art" - /> - <StyledTab - label="Auswertung" - value="Auswertung" - data-id="Auswertung" - /> - <StyledTab - label="Dateien" - value="Dateien" - data-id="Dateien" - /> - <StyledTab - label="Historien" - value="Historien" - data-id="Historien" - /> - </Tabs> - <TabContentContainer> - <TabContent> - {loading ? - <Spinner /> - : error ? - <Error error={error} /> - : <Suspense fallback={<Spinner />}> - <Outlet context={{ data }} /> - </Suspense> - } - </TabContent> - </TabContentContainer> - </Container> - </ErrorBoundary> - ) -} diff --git a/src/components/Projekte/Daten/Apart/Menu.jsx b/src/components/Projekte/Daten/Apart/Menu.jsx index 10710bbcab..7015501dfb 100644 --- a/src/components/Projekte/Daten/Apart/Menu.jsx +++ b/src/components/Projekte/Daten/Apart/Menu.jsx @@ -13,7 +13,7 @@ import isEqual from 'lodash/isEqual' import { MenuBar } from '../../../shared/MenuBar/index.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' const iconStyle = { color: 'white' } @@ -25,7 +25,7 @@ export const Menu = memo( const client = useApolloClient() const queryClient = useQueryClient() const { projId, apId, taxonId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result @@ -57,6 +57,9 @@ export const Menu = memo( queryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + queryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) const id = result?.data?.createApart?.apart?.id navigate(`/Daten/Projekte/${projId}/Arten/${apId}/Taxa/${id}${search}`) }, [apId, client, store, queryClient, navigate, search, projId]) @@ -102,6 +105,9 @@ export const Menu = memo( queryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + queryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) // navigate to parent navigate(`/Daten/Projekte/${projId}/Arten/${apId}/Taxa${search}`) }, [ diff --git a/src/components/Projekte/Daten/Apart/index.jsx b/src/components/Projekte/Daten/Apart/index.jsx index 448b8ced72..7f857298e6 100644 --- a/src/components/Projekte/Daten/Apart/index.jsx +++ b/src/components/Projekte/Daten/Apart/index.jsx @@ -9,7 +9,7 @@ import { SelectLoadingOptions } from '../../../shared/SelectLoadingOptions.jsx' import { FormTitle } from '../../../shared/FormTitle/index.jsx' import { query } from './query.js' import { queryAeTaxonomies } from './queryAeTaxonomies.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { apart } from '../../../shared/fragments.js' @@ -45,7 +45,7 @@ export const Component = memo( observer(() => { const { taxonId: id } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const client = useApolloClient() const queryClient = useQueryClient() @@ -140,7 +140,7 @@ export const Component = memo( <Container> <FormTitle title="Taxon" - menuBar={<Menu />} + MenuBarComponent={Menu} /> <FieldsContainer> <div> diff --git a/src/components/Projekte/Daten/Aparts/List.jsx b/src/components/Projekte/Daten/Aparts/List.jsx new file mode 100644 index 0000000000..209757b8eb --- /dev/null +++ b/src/components/Projekte/Daten/Aparts/List.jsx @@ -0,0 +1,30 @@ +import { memo, useContext } from 'react' +import { observer } from 'mobx-react-lite' + +import { MobxContext } from '../../../../mobxContext.js' +import { useApartsNavData } from '../../../../modules/useApartsNavData.js' +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from './Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' + +export const List = memo( + observer(() => { + const store = useContext(MobxContext) + const { nodeLabelFilter } = store.tree + + const { navData, isLoading, error } = useApartsNavData() + + if (isLoading) return <Spinner /> + + if (error) return <Error error={error} /> + + return ( + <SharedList + navData={navData} + MenuBarComponent={Menu} + highlightSearchString={nodeLabelFilter.apart} + /> + ) + }), +) diff --git a/src/components/Projekte/Daten/Aparts/Menu.jsx b/src/components/Projekte/Daten/Aparts/Menu.jsx index aceb99a864..621307efcd 100644 --- a/src/components/Projekte/Daten/Aparts/Menu.jsx +++ b/src/components/Projekte/Daten/Aparts/Menu.jsx @@ -7,24 +7,22 @@ import { MdContentCopy } from 'react-icons/md' import IconButton from '@mui/material/IconButton' import Tooltip from '@mui/material/Tooltip' import { observer } from 'mobx-react-lite' -import { useAtom } from 'jotai' import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' +import { FilterButton } from '../../../shared/MenuBar/FilterButton.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' -import { LabelFilter, labelFilterWidth } from '../../../shared/LabelFilter.jsx' -import { listLabelFilterIsIconAtom } from '../../../../JotaiStore/index.js' +import { MobxContext } from '../../../../mobxContext.js' const iconStyle = { color: 'white' } export const Menu = memo( - observer(() => { + observer(({ toggleFilterInput }) => { const { search } = useLocation() const navigate = useNavigate() const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { apId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result @@ -56,18 +54,19 @@ export const Menu = memo( tanstackQueryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + tanstackQueryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) const id = result?.data?.createApart?.apart?.id navigate(`./${id}${search}`) }, [client, store, tanstackQueryClient, navigate, search, apId]) - const [labelFilterIsIcon] = useAtom(listLabelFilterIsIconAtom) - return ( <ErrorBoundary> <MenuBar> - <LabelFilter - width={labelFilterIsIcon ? buttonWidth : labelFilterWidth} - /> + {!!toggleFilterInput && ( + <FilterButton toggleFilterInput={toggleFilterInput} /> + )} <Tooltip title="Neues Taxon erstellen"> <IconButton onClick={onClickAdd}> <FaPlus style={iconStyle} /> diff --git a/src/components/Projekte/Daten/Aparts/index.jsx b/src/components/Projekte/Daten/Aparts/index.jsx index 6de0d482e5..ea4a216067 100644 --- a/src/components/Projekte/Daten/Aparts/index.jsx +++ b/src/components/Projekte/Daten/Aparts/index.jsx @@ -1,46 +1,14 @@ -import { memo, useContext } from 'react' -import { useApolloClient, gql } from '@apollo/client' -import { useQuery } from '@tanstack/react-query' -import { observer } from 'mobx-react-lite' -import { useParams } from 'react-router' +import { memo } from 'react' +import { useAtom } from 'jotai' -import { StoreContext } from '../../../../storeContext.js' -import { createApsQuery } from '../../../../modules/createApsQuery.js' -import { createApartsQuery } from '../../../../modules/createApartsQuery.js' -import { List } from '../../../shared/List/index.jsx' -import { Menu } from './Menu.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Error } from '../../../shared/Error.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Ap } from '../Ap/index.jsx' +import { List } from './List.jsx' -export const Component = memo( - observer(() => { - const { apId } = useParams() - const apolloClient = useApolloClient() - const store = useContext(StoreContext) - const { apartGqlFilterForTree, nodeLabelFilter } = store.tree +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) - const { data, isLoading, error } = useQuery( - createApartsQuery({ - apId, - apartGqlFilterForTree, - apolloClient, - }), - ) - const aparts = data?.data?.apById?.apartsByApId?.nodes ?? [] - const totalCount = data?.data?.apById?.apartsCount?.totalCount ?? 0 + if (isDesktopView) return <Ap /> - if (isLoading) return <Spinner /> - - if (error) return <Error error={error} /> - - return ( - <List - items={aparts} - title="Taxa" - totalCount={totalCount} - menuBar={<Menu />} - highlightSearchString={nodeLabelFilter.apart} - /> - ) - }), -) + return <List /> +}) diff --git a/src/components/Projekte/Daten/Apber/Menu.jsx b/src/components/Projekte/Daten/Apber/Menu.jsx index 8d599d6af3..f0461f1bdb 100644 --- a/src/components/Projekte/Daten/Apber/Menu.jsx +++ b/src/components/Projekte/Daten/Apber/Menu.jsx @@ -13,7 +13,7 @@ import isEqual from 'lodash/isEqual' import { MenuBar } from '../../../shared/MenuBar/index.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' const iconStyle = { color: 'white' } @@ -25,7 +25,7 @@ export const Menu = memo( const client = useApolloClient() const queryClient = useQueryClient() const { projId, apId, apberId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result @@ -57,6 +57,9 @@ export const Menu = memo( queryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + queryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) const id = result?.data?.createApber?.apber?.id navigate( `/Daten/Projekte/${projId}/Arten/${apId}/AP-Berichte/${id}${search}`, @@ -104,6 +107,9 @@ export const Menu = memo( queryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + queryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) // navigate to parent navigate(`/Daten/Projekte/${projId}/Arten/${apId}/AP-Berichte${search}`) }, [ diff --git a/src/components/Projekte/Daten/Apber/index.jsx b/src/components/Projekte/Daten/Apber/index.jsx index 34120e0cf7..8f46079482 100644 --- a/src/components/Projekte/Daten/Apber/index.jsx +++ b/src/components/Projekte/Daten/Apber/index.jsx @@ -14,7 +14,7 @@ import { DateField } from '../../../shared/Date.jsx' import { FormTitle } from '../../../shared/FormTitle/index.jsx' import { constants } from '../../../../modules/constants.js' import { query } from './query.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { apber } from '../../../shared/fragments.js' @@ -70,7 +70,7 @@ export const Component = memo( observer(() => { const { apberId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const client = useApolloClient() const queryClient = useQueryClient() @@ -140,7 +140,7 @@ export const Component = memo( <Container> <FormTitle title="AP-Bericht" - menuBar={<Menu />} + MenuBarComponent={Menu} /> <FormContainer> <TextField diff --git a/src/components/Projekte/Daten/Apbers/List.jsx b/src/components/Projekte/Daten/Apbers/List.jsx new file mode 100644 index 0000000000..ec0a544b46 --- /dev/null +++ b/src/components/Projekte/Daten/Apbers/List.jsx @@ -0,0 +1,34 @@ +import { memo, useContext } from 'react' +import { observer } from 'mobx-react-lite' +import { useAtom } from 'jotai' + +import { MobxContext } from '../../../../mobxContext.js' +import { useApbersNavData } from '../../../../modules/useApbersNavData.js' +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from './Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' + +export const List = memo( + observer(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) + + const store = useContext(MobxContext) + const { nodeLabelFilter } = store.tree + + const { navData, isLoading, error } = useApbersNavData() + + if (isLoading) return <Spinner /> + + if (error) return <Error error={error} /> + + return ( + <SharedList + navData={navData} + MenuBarComponent={Menu} + highlightSearchString={nodeLabelFilter.apber} + /> + ) + }), +) diff --git a/src/components/Projekte/Daten/Apbers/Menu.jsx b/src/components/Projekte/Daten/Apbers/Menu.jsx index e8642f68f9..3fa3993095 100644 --- a/src/components/Projekte/Daten/Apbers/Menu.jsx +++ b/src/components/Projekte/Daten/Apbers/Menu.jsx @@ -6,24 +6,22 @@ import { FaPlus } from 'react-icons/fa6' import IconButton from '@mui/material/IconButton' import Tooltip from '@mui/material/Tooltip' import { observer } from 'mobx-react-lite' -import { useAtom } from 'jotai' import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' +import { FilterButton } from '../../../shared/MenuBar/FilterButton.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' -import { LabelFilter, labelFilterWidth } from '../../../shared/LabelFilter.jsx' -import { listLabelFilterIsIconAtom } from '../../../../JotaiStore/index.js' +import { MobxContext } from '../../../../mobxContext.js' const iconStyle = { color: 'white' } export const Menu = memo( - observer(() => { + observer(({ toggleFilterInput }) => { const { search } = useLocation() const navigate = useNavigate() const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { apId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result @@ -55,18 +53,19 @@ export const Menu = memo( tanstackQueryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + tanstackQueryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) const id = result?.data?.createApber?.apber?.id navigate(`./${id}${search}`) }, [client, store, tanstackQueryClient, navigate, search, apId]) - const [labelFilterIsIcon] = useAtom(listLabelFilterIsIconAtom) - return ( <ErrorBoundary> <MenuBar> - <LabelFilter - width={labelFilterIsIcon ? buttonWidth : labelFilterWidth} - /> + {!!toggleFilterInput && ( + <FilterButton toggleFilterInput={toggleFilterInput} /> + )} <Tooltip title="Neuen AP-Bericht erstellen"> <IconButton onClick={onClickAdd}> <FaPlus style={iconStyle} /> diff --git a/src/components/Projekte/Daten/Apbers/index.jsx b/src/components/Projekte/Daten/Apbers/index.jsx index 20efe6e104..ea4a216067 100644 --- a/src/components/Projekte/Daten/Apbers/index.jsx +++ b/src/components/Projekte/Daten/Apbers/index.jsx @@ -1,46 +1,14 @@ -import { memo, useContext } from 'react' -import { useApolloClient, gql } from '@apollo/client' -import { useQuery } from '@tanstack/react-query' -import { observer } from 'mobx-react-lite' -import { useParams } from 'react-router' +import { memo } from 'react' +import { useAtom } from 'jotai' -import { StoreContext } from '../../../../storeContext.js' -import { createApsQuery } from '../../../../modules/createApsQuery.js' -import { createApbersQuery } from '../../../../modules/createApbersQuery.js' -import { List } from '../../../shared/List/index.jsx' -import { Menu } from './Menu.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Error } from '../../../shared/Error.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Ap } from '../Ap/index.jsx' +import { List } from './List.jsx' -export const Component = memo( - observer(() => { - const { apId } = useParams() - const apolloClient = useApolloClient() - const store = useContext(StoreContext) - const { apberGqlFilterForTree, nodeLabelFilter } = store.tree +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) - const { data, isLoading, error } = useQuery( - createApbersQuery({ - apId, - apberGqlFilterForTree, - apolloClient, - }), - ) - const apbers = data?.data?.apById?.apbersByApId?.nodes ?? [] - const totalCount = data?.data?.apById?.apbersCount?.totalCount ?? 0 + if (isDesktopView) return <Ap /> - if (isLoading) return <Spinner /> - - if (error) return <Error error={error} /> - - return ( - <List - items={apbers} - title="AP-Berichte" - totalCount={totalCount} - menuBar={<Menu />} - highlightSearchString={nodeLabelFilter.apber} - /> - ) - }), -) + return <List /> +}) diff --git a/src/components/Projekte/Daten/Apberuebersicht/Menu.jsx b/src/components/Projekte/Daten/Apberuebersicht/Menu.jsx index 167805f865..0a0caa4560 100644 --- a/src/components/Projekte/Daten/Apberuebersicht/Menu.jsx +++ b/src/components/Projekte/Daten/Apberuebersicht/Menu.jsx @@ -13,7 +13,7 @@ import isEqual from 'lodash/isEqual' import { MenuBar } from '../../../shared/MenuBar/index.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' const iconStyle = { color: 'white' } @@ -25,7 +25,7 @@ export const Menu = memo( const client = useApolloClient() const queryClient = useQueryClient() const { projId, apberUebersichtId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result diff --git a/src/components/Projekte/Daten/Apberuebersicht/index.jsx b/src/components/Projekte/Daten/Apberuebersicht/index.jsx index c894df173c..ba40bb630b 100644 --- a/src/components/Projekte/Daten/Apberuebersicht/index.jsx +++ b/src/components/Projekte/Daten/Apberuebersicht/index.jsx @@ -14,7 +14,7 @@ import { MarkdownField } from '../../../shared/MarkdownField/index.jsx' import { TextFieldNonUpdatable } from '../../../shared/TextFieldNonUpdatable.jsx' import { FormTitle } from '../../../shared/FormTitle/index.jsx' import { query } from './query.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { apberuebersicht } from '../../../shared/fragments.js' @@ -73,7 +73,7 @@ export const Component = memo( observer(() => { const { apberUebersichtId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const client = useApolloClient() const { user } = store const { token } = user @@ -190,7 +190,7 @@ export const Component = memo( <Container> <FormTitle title="AP-Bericht Jahresübersicht" - menuBar={<Menu />} + MenuBarComponent={Menu} /> <FieldsContainer> <FormContainer> diff --git a/src/components/Projekte/Daten/Apberuebersichts/List.jsx b/src/components/Projekte/Daten/Apberuebersichts/List.jsx new file mode 100644 index 0000000000..2f82d6b6a1 --- /dev/null +++ b/src/components/Projekte/Daten/Apberuebersichts/List.jsx @@ -0,0 +1,30 @@ +import { memo, useContext } from 'react' +import { observer } from 'mobx-react-lite' + +import { MobxContext } from '../../../../mobxContext.js' +import { useApberuebersichtsNavData } from '../../../../modules/useApberuebersichtsNavData.js' +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from './Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' + +export const List = memo( + observer(() => { + const store = useContext(MobxContext) + const { nodeLabelFilter } = store.tree + + const { navData, isLoading, error } = useApberuebersichtsNavData() + + if (isLoading) return <Spinner /> + + if (error) return <Error error={error} /> + + return ( + <SharedList + navData={navData} + MenuBarComponent={Menu} + highlightSearchString={nodeLabelFilter.apberuebersicht} + /> + ) + }), +) diff --git a/src/components/Projekte/Daten/Apberuebersichts/Menu.jsx b/src/components/Projekte/Daten/Apberuebersichts/Menu.jsx index 9cf5aeb006..8c2408ae8b 100644 --- a/src/components/Projekte/Daten/Apberuebersichts/Menu.jsx +++ b/src/components/Projekte/Daten/Apberuebersichts/Menu.jsx @@ -10,17 +10,15 @@ import IconButton from '@mui/material/IconButton' import Tooltip from '@mui/material/Tooltip' import styled from '@emotion/styled' import { observer } from 'mobx-react-lite' -import { useAtom } from 'jotai' import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' +import { FilterButton } from '../../../shared/MenuBar/FilterButton.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' import { moveTo } from '../../../../modules/moveTo/index.js' import { copyTo } from '../../../../modules/copyTo/index.js' import { closeLowerNodes } from '../../TreeContainer/closeLowerNodes.js' -import { StoreContext } from '../../../../storeContext.js' -import { LabelFilter, labelFilterWidth } from '../../../shared/LabelFilter.jsx' -import { listLabelFilterIsIconAtom } from '../../../../JotaiStore/index.js' +import { MobxContext } from '../../../../mobxContext.js' const Fitter = styled.div` margin-top: -15px; @@ -29,13 +27,13 @@ const Fitter = styled.div` const iconStyle = { color: 'white' } export const Menu = memo( - observer(() => { + observer(({ toggleFilterInput }) => { const { search, pathname } = useLocation() const navigate = useNavigate() const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { projId, apberuebersichtId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result @@ -75,14 +73,12 @@ export const Menu = memo( navigate(`./${id}${search}`) }, [projId, client, store, tanstackQueryClient, navigate, search]) - const [labelFilterIsIcon] = useAtom(listLabelFilterIsIconAtom) - return ( <ErrorBoundary> <MenuBar> - <LabelFilter - width={labelFilterIsIcon ? buttonWidth : labelFilterWidth} - /> + {!!toggleFilterInput && ( + <FilterButton toggleFilterInput={toggleFilterInput} /> + )} <Tooltip title="Neuen AP-Bericht erstellen"> <IconButton onClick={onClickAdd}> <FaPlus style={iconStyle} /> diff --git a/src/components/Projekte/Daten/Apberuebersichts/index.jsx b/src/components/Projekte/Daten/Apberuebersichts/index.jsx index 13a9305ea5..ae215e2a53 100644 --- a/src/components/Projekte/Daten/Apberuebersichts/index.jsx +++ b/src/components/Projekte/Daten/Apberuebersichts/index.jsx @@ -1,45 +1,14 @@ -import { memo, useContext } from 'react' -import { useApolloClient, gql } from '@apollo/client' -import { useQuery } from '@tanstack/react-query' -import { observer } from 'mobx-react-lite' -import { useParams } from 'react-router' +import { memo } from 'react' +import { useAtom } from 'jotai' -import { StoreContext } from '../../../../storeContext.js' -import { createApberuebersichtsQuery } from '../../../../modules/createApberuebersichtsQuery.js' -import { List } from '../../../shared/List/index.jsx' -import { Menu } from './Menu.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Error } from '../../../shared/Error.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Projekt } from '../Projekt/index.jsx' +import { List } from './List.jsx' -export const Component = memo( - observer(() => { - const { projId } = useParams() - const apolloClient = useApolloClient() - const store = useContext(StoreContext) - const { apberuebersichtGqlFilterForTree, nodeLabelFilter } = store.tree +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) - const { data, isLoading, error } = useQuery( - createApberuebersichtsQuery({ - projId, - apberuebersichtGqlFilterForTree, - apolloClient, - }), - ) - const apberuebersichts = data?.data?.allApberuebersichts?.nodes ?? [] - const totalCount = data?.data?.totalCount?.totalCount ?? 0 + if (isDesktopView) return <Projekt /> - if (isLoading) return <Spinner /> - - if (error) return <Error error={error} /> - - return ( - <List - items={apberuebersichts} - title="AP-Berichte" - totalCount={totalCount} - menuBar={<Menu />} - highlightSearchString={nodeLabelFilter.apberuebersicht} - /> - ) - }), -) + return <List /> +}) diff --git a/src/components/Projekte/Daten/Aps/List.jsx b/src/components/Projekte/Daten/Aps/List.jsx new file mode 100644 index 0000000000..c30ebac2f2 --- /dev/null +++ b/src/components/Projekte/Daten/Aps/List.jsx @@ -0,0 +1,30 @@ +import { memo, useContext } from 'react' +import { observer } from 'mobx-react-lite' + +import { MobxContext } from '../../../../mobxContext.js' +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from './Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' +import { useApsNavData } from '../../../../modules/useApsNavData.js' + +export const List = memo( + observer(() => { + const store = useContext(MobxContext) + const { nodeLabelFilter } = store.tree + + const { navData, isLoading, error } = useApsNavData() + + if (isLoading) return <Spinner /> + + if (error) return <Error error={error} /> + + return ( + <SharedList + navData={navData} + MenuBarComponent={Menu} + highlightSearchString={nodeLabelFilter.ap} + /> + ) + }), +) diff --git a/src/components/Projekte/Daten/Aps/Menu.jsx b/src/components/Projekte/Daten/Aps/Menu.jsx index 2a006f3f76..ad330c485a 100644 --- a/src/components/Projekte/Daten/Aps/Menu.jsx +++ b/src/components/Projekte/Daten/Aps/Menu.jsx @@ -10,18 +10,16 @@ import { BsSignStopFill } from 'react-icons/bs' import IconButton from '@mui/material/IconButton' import Tooltip from '@mui/material/Tooltip' import styled from '@emotion/styled' -import { useAtom } from 'jotai' import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' +import { FilterButton } from '../../../shared/MenuBar/FilterButton.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' import { moveTo } from '../../../../modules/moveTo/index.js' import { copyTo } from '../../../../modules/copyTo/index.js' import { closeLowerNodes } from '../../TreeContainer/closeLowerNodes.js' import { ApFilter } from '../../TreeContainer/ApFilter/index.jsx' -import { StoreContext } from '../../../../storeContext.js' -import { LabelFilter, labelFilterWidth } from '../../../shared/LabelFilter.jsx' -import { listLabelFilterIsIconAtom } from '../../../../JotaiStore/index.js' +import { MobxContext } from '../../../../mobxContext.js' const Fitter = styled.div` margin-top: ${(props) => (props.inmenu === 'true' ? -8 : -15)}px; @@ -30,13 +28,13 @@ const Fitter = styled.div` const iconStyle = { color: 'white' } export const Menu = memo( - observer(() => { + observer(({ toggleFilterInput }) => { const { search, pathname } = useLocation() const navigate = useNavigate() const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { projId, apId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { setMoving, moving, copying, setCopying } = store @@ -122,14 +120,12 @@ export const Menu = memo( const isMoving = !!moving.table const isCopying = !!copying.table - const [labelFilterIsIcon] = useAtom(listLabelFilterIsIconAtom) - return ( <ErrorBoundary> <MenuBar rerenderer={`${moving.id}/${copying.id}`}> - <LabelFilter - width={labelFilterIsIcon ? buttonWidth : labelFilterWidth} - /> + {!!toggleFilterInput && ( + <FilterButton toggleFilterInput={toggleFilterInput} /> + )} <Tooltip title="Neue Art erstellen"> <IconButton onClick={onClickAdd}> <FaPlus style={iconStyle} /> diff --git a/src/components/Projekte/Daten/Aps/index.jsx b/src/components/Projekte/Daten/Aps/index.jsx index 529bde0e01..ae215e2a53 100644 --- a/src/components/Projekte/Daten/Aps/index.jsx +++ b/src/components/Projekte/Daten/Aps/index.jsx @@ -1,45 +1,14 @@ -import { memo, useContext } from 'react' -import { useApolloClient, gql } from '@apollo/client' -import { useQuery } from '@tanstack/react-query' -import { observer } from 'mobx-react-lite' -import { useParams } from 'react-router' +import { memo } from 'react' +import { useAtom } from 'jotai' -import { StoreContext } from '../../../../storeContext.js' -import { createApsQuery } from '../../../../modules/createApsQuery.js' -import { List } from '../../../shared/List/index.jsx' -import { Menu } from './Menu.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Error } from '../../../shared/Error.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Projekt } from '../Projekt/index.jsx' +import { List } from './List.jsx' -export const Component = memo( - observer(() => { - const { projId } = useParams() - const apolloClient = useApolloClient() - const store = useContext(StoreContext) - const { apGqlFilterForTree, nodeLabelFilter } = store.tree +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) - const { data, isLoading, error } = useQuery( - createApsQuery({ - projId, - apGqlFilterForTree, - apolloClient, - }), - ) - const aps = data?.data?.allAps?.nodes ?? [] - const totalCount = data?.data?.totalCount?.totalCount ?? 0 + if (isDesktopView) return <Projekt /> - if (isLoading) return <Spinner /> - - if (error) return <Error error={error} /> - - return ( - <List - items={aps} - title="Arten" - totalCount={totalCount} - menuBar={<Menu />} - highlightSearchString={nodeLabelFilter.ap} - /> - ) - }), -) + return <List /> +}) diff --git a/src/components/Projekte/Daten/Assozart/Menu.jsx b/src/components/Projekte/Daten/Assozart/Menu.jsx index c4ed092e83..8ad9a8b251 100644 --- a/src/components/Projekte/Daten/Assozart/Menu.jsx +++ b/src/components/Projekte/Daten/Assozart/Menu.jsx @@ -13,7 +13,7 @@ import isEqual from 'lodash/isEqual' import { MenuBar } from '../../../shared/MenuBar/index.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' const iconStyle = { color: 'white' } @@ -25,7 +25,7 @@ export const Menu = memo( const client = useApolloClient() const queryClient = useQueryClient() const { projId, apId, assozartId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result @@ -57,6 +57,9 @@ export const Menu = memo( queryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + queryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) const id = result?.data?.createAssozart?.assozart?.id navigate( `/Daten/Projekte/${projId}/Arten/${apId}/assoziierte-Arten/${id}${search}`, @@ -104,6 +107,9 @@ export const Menu = memo( queryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + queryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) // navigate to parent navigate( `/Daten/Projekte/${projId}/Arten/${apId}/assoziierte-Arten${search}`, diff --git a/src/components/Projekte/Daten/Assozart/index.jsx b/src/components/Projekte/Daten/Assozart/index.jsx index c0b082ac06..3cb34d03a3 100644 --- a/src/components/Projekte/Daten/Assozart/index.jsx +++ b/src/components/Projekte/Daten/Assozart/index.jsx @@ -10,7 +10,7 @@ import { SelectLoadingOptions } from '../../../shared/SelectLoadingOptions.jsx' import { FormTitle } from '../../../shared/FormTitle/index.jsx' import { query } from './query.js' import { queryAeTaxonomies } from './queryAeTaxonomies.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { assozart } from '../../../shared/fragments.js' @@ -45,7 +45,7 @@ export const Component = memo( const { assozartId: id } = useParams() const client = useApolloClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const queryClient = useQueryClient() const [fieldErrors, setFieldErrors] = useState({}) @@ -143,7 +143,7 @@ export const Component = memo( <Container data-id="assozart"> <FormTitle title="assoziierte Art" - menuBar={<Menu />} + MenuBarComponent={Menu} /> <FormContainer> <SelectLoadingOptions diff --git a/src/components/Projekte/Daten/Assozarts/List.jsx b/src/components/Projekte/Daten/Assozarts/List.jsx new file mode 100644 index 0000000000..ced4bbc47b --- /dev/null +++ b/src/components/Projekte/Daten/Assozarts/List.jsx @@ -0,0 +1,30 @@ +import { memo, useContext } from 'react' +import { observer } from 'mobx-react-lite' + +import { MobxContext } from '../../../../mobxContext.js' +import { useAssozartsNavData } from '../../../../modules/useAssozartsNavData.js' +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from './Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' + +export const List = memo( + observer(() => { + const store = useContext(MobxContext) + const { nodeLabelFilter } = store.tree + + const { navData, isLoading, error } = useAssozartsNavData() + + if (isLoading) return <Spinner /> + + if (error) return <Error error={error} /> + + return ( + <SharedList + navData={navData} + MenuBarComponent={Menu} + highlightSearchString={nodeLabelFilter.assozart} + /> + ) + }), +) diff --git a/src/components/Projekte/Daten/Assozarts/Menu.jsx b/src/components/Projekte/Daten/Assozarts/Menu.jsx index 808ac127de..957ba687ea 100644 --- a/src/components/Projekte/Daten/Assozarts/Menu.jsx +++ b/src/components/Projekte/Daten/Assozarts/Menu.jsx @@ -7,24 +7,22 @@ import { MdContentCopy } from 'react-icons/md' import IconButton from '@mui/material/IconButton' import Tooltip from '@mui/material/Tooltip' import { observer } from 'mobx-react-lite' -import { useAtom } from 'jotai' import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' +import { FilterButton } from '../../../shared/MenuBar/FilterButton.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' -import { LabelFilter, labelFilterWidth } from '../../../shared/LabelFilter.jsx' -import { listLabelFilterIsIconAtom } from '../../../../JotaiStore/index.js' +import { MobxContext } from '../../../../mobxContext.js' const iconStyle = { color: 'white' } export const Menu = memo( - observer(() => { + observer(({ toggleFilterInput }) => { const { search } = useLocation() const navigate = useNavigate() const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { apId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result @@ -56,18 +54,19 @@ export const Menu = memo( tanstackQueryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + tanstackQueryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) const id = result?.data?.createAssozart?.assozart?.id navigate(`./${id}${search}`) }, [client, store, tanstackQueryClient, navigate, search, apId]) - const [labelFilterIsIcon] = useAtom(listLabelFilterIsIconAtom) - return ( <ErrorBoundary> <MenuBar> - <LabelFilter - width={labelFilterIsIcon ? buttonWidth : labelFilterWidth} - /> + {!!toggleFilterInput && ( + <FilterButton toggleFilterInput={toggleFilterInput} /> + )} <Tooltip title="Neues assoziierte Art erstellen"> <IconButton onClick={onClickAdd}> <FaPlus style={iconStyle} /> diff --git a/src/components/Projekte/Daten/Assozarts/index.jsx b/src/components/Projekte/Daten/Assozarts/index.jsx index 6153ffed8d..ea4a216067 100644 --- a/src/components/Projekte/Daten/Assozarts/index.jsx +++ b/src/components/Projekte/Daten/Assozarts/index.jsx @@ -1,46 +1,14 @@ -import { memo, useContext } from 'react' -import { useApolloClient, gql } from '@apollo/client' -import { useQuery } from '@tanstack/react-query' -import { observer } from 'mobx-react-lite' -import { useParams } from 'react-router' +import { memo } from 'react' +import { useAtom } from 'jotai' -import { StoreContext } from '../../../../storeContext.js' -import { createApsQuery } from '../../../../modules/createApsQuery.js' -import { createAssozartsQuery } from '../../../../modules/createAssozartsQuery.js' -import { List } from '../../../shared/List/index.jsx' -import { Menu } from './Menu.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Error } from '../../../shared/Error.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Ap } from '../Ap/index.jsx' +import { List } from './List.jsx' -export const Component = memo( - observer(() => { - const { apId } = useParams() - const apolloClient = useApolloClient() - const store = useContext(StoreContext) - const { assozartGqlFilterForTree, nodeLabelFilter } = store.tree +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) - const { data, isLoading, error } = useQuery( - createAssozartsQuery({ - apId, - assozartGqlFilterForTree, - apolloClient, - }), - ) - const assozarts = data?.data?.apById?.assozartsByApId?.nodes ?? [] - const totalCount = data?.data?.apById?.assozartsCount?.totalCount ?? 0 + if (isDesktopView) return <Ap /> - if (isLoading) return <Spinner /> - - if (error) return <Error error={error} /> - - return ( - <List - items={assozarts} - title="Assoziierte Arten" - totalCount={totalCount} - menuBar={<Menu />} - highlightSearchString={nodeLabelFilter.assozart} - /> - ) - }), -) + return <List /> +}) diff --git a/src/components/Projekte/Daten/Beob/index.jsx b/src/components/Projekte/Daten/Beob/index.jsx index 2612f125db..316277a6c1 100644 --- a/src/components/Projekte/Daten/Beob/index.jsx +++ b/src/components/Projekte/Daten/Beob/index.jsx @@ -15,7 +15,7 @@ import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { Error } from '../../../shared/Error.jsx' import { Spinner } from '../../../shared/Spinner.jsx' import { Field as BeobField } from './Field.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' const OuterContainer = styled.div` container-type: inline-size; @@ -41,7 +41,7 @@ export const Beob = memo( const { beobId: id } = useParams() const client = useApolloClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { sortedBeobFields: sortedBeobFieldsPassed, setSortedBeobFields } = store const sortedBeobFields = sortedBeobFieldsPassed.slice() diff --git a/src/components/Projekte/Daten/BeobNichtBeurteilts/List.jsx b/src/components/Projekte/Daten/BeobNichtBeurteilts/List.jsx new file mode 100644 index 0000000000..884dcceeeb --- /dev/null +++ b/src/components/Projekte/Daten/BeobNichtBeurteilts/List.jsx @@ -0,0 +1,33 @@ +import { memo, useContext } from 'react' +import { observer } from 'mobx-react-lite' + +import { MobxContext } from '../../../../mobxContext.js' +import { useBeobNichtBeurteiltsNavData } from '../../../../modules/useBeobNichtBeurteiltsNavData.js' +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from './Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' + +const menuBarProps = { apfloraLayer: 'beobNichtBeurteilt' } + +export const List = memo( + observer(() => { + const store = useContext(MobxContext) + const { nodeLabelFilter } = store.tree + + const { navData, isLoading, error } = useBeobNichtBeurteiltsNavData() + + if (isLoading) return <Spinner /> + + if (error) return <Error error={error} /> + + return ( + <SharedList + navData={navData} + MenuBarComponent={Menu} + menuBarProps={menuBarProps} + highlightSearchString={nodeLabelFilter.beob} + /> + ) + }), +) diff --git a/src/components/Projekte/Daten/Beobzuordnungs/Menu.jsx b/src/components/Projekte/Daten/BeobNichtBeurteilts/Menu.jsx similarity index 64% rename from src/components/Projekte/Daten/Beobzuordnungs/Menu.jsx rename to src/components/Projekte/Daten/BeobNichtBeurteilts/Menu.jsx index f2fdb426d2..58f84a0549 100644 --- a/src/components/Projekte/Daten/Beobzuordnungs/Menu.jsx +++ b/src/components/Projekte/Daten/BeobNichtBeurteilts/Menu.jsx @@ -5,29 +5,25 @@ import IconButton from '@mui/material/IconButton' import Tooltip from '@mui/material/Tooltip' import uniq from 'lodash/uniq' import { observer } from 'mobx-react-lite' -import { useAtom } from 'jotai' import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' +import { FilterButton } from '../../../shared/MenuBar/FilterButton.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { isMobilePhone } from '../../../../modules/isMobilePhone.js' -import { useSearchParamsState } from '../../../../modules/useSearchParamsState.js' -import { StoreContext } from '../../../../storeContext.js' -import { LabelFilter, labelFilterWidth } from '../../../shared/LabelFilter.jsx' -import { listLabelFilterIsIconAtom } from '../../../../JotaiStore/index.js' +import { useProjekteTabs } from '../../../../modules/useProjekteTabs.js' +import { MobxContext } from '../../../../mobxContext.js' const iconStyle = { color: 'white' } +// TODO: how to pass both props? +// TODO: need to add menu to other beobs to enable filtering export const Menu = memo( - observer(({ apfloraLayer }) => { + observer(({ apfloraLayer, toggleFilterInput }) => { const tanstackQueryClient = useQueryClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { setActiveApfloraLayers, activeApfloraLayers } = store - const [projekteTabs, setProjekteTabs] = useSearchParamsState( - 'projekteTabs', - isMobilePhone() ? ['tree'] : ['tree', 'daten'], - ) + const [projekteTabs, setProjekteTabs] = useProjekteTabs() const showMapIfNotYetVisible = useCallback( (projekteTabs) => { const isVisible = projekteTabs.includes('karte') @@ -47,14 +43,12 @@ export const Menu = memo( activeApfloraLayers, ]) - const [labelFilterIsIcon] = useAtom(listLabelFilterIsIconAtom) - return ( <ErrorBoundary> <MenuBar> - <LabelFilter - width={labelFilterIsIcon ? buttonWidth : labelFilterWidth} - /> + {!!toggleFilterInput && ( + <FilterButton toggleFilterInput={toggleFilterInput} /> + )} <Tooltip title="Zeige auf Karte"> <IconButton onClick={onClickShowOnMap}> <FaMap style={iconStyle} /> diff --git a/src/components/Projekte/Daten/BeobNichtBeurteilts/index.jsx b/src/components/Projekte/Daten/BeobNichtBeurteilts/index.jsx new file mode 100644 index 0000000000..ea4a216067 --- /dev/null +++ b/src/components/Projekte/Daten/BeobNichtBeurteilts/index.jsx @@ -0,0 +1,14 @@ +import { memo } from 'react' +import { useAtom } from 'jotai' + +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Ap } from '../Ap/index.jsx' +import { List } from './List.jsx' + +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) + + if (isDesktopView) return <Ap /> + + return <List /> +}) diff --git a/src/components/Projekte/Daten/BeobNichtZuzuordnens/List.jsx b/src/components/Projekte/Daten/BeobNichtZuzuordnens/List.jsx new file mode 100644 index 0000000000..55954cc661 --- /dev/null +++ b/src/components/Projekte/Daten/BeobNichtZuzuordnens/List.jsx @@ -0,0 +1,33 @@ +import { memo, useContext } from 'react' +import { observer } from 'mobx-react-lite' + +import { MobxContext } from '../../../../mobxContext.js' +import { useBeobNichtZuzuordnensNavData } from '../../../../modules/useBeobNichtZuzuordnensNavData.js' +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from '../BeobNichtBeurteilts/Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' + +const menuBarProps = { apfloraLayer: 'beobNichtZuzuordnen' } + +export const List = memo( + observer(() => { + const store = useContext(MobxContext) + const { nodeLabelFilter } = store.tree + + const { navData, isLoading, error } = useBeobNichtZuzuordnensNavData() + + if (isLoading) return <Spinner /> + + if (error) return <Error error={error} /> + + return ( + <SharedList + navData={navData} + MenuBarComponent={Menu} + menuBarProps={menuBarProps} + highlightSearchString={nodeLabelFilter.beob} + /> + ) + }), +) diff --git a/src/components/Projekte/Daten/BeobNichtZuzuordnens/Menu.jsx b/src/components/Projekte/Daten/BeobNichtZuzuordnens/Menu.jsx new file mode 100644 index 0000000000..58f84a0549 --- /dev/null +++ b/src/components/Projekte/Daten/BeobNichtZuzuordnens/Menu.jsx @@ -0,0 +1,61 @@ +import { memo, useCallback, useContext } from 'react' +import { useQueryClient } from '@tanstack/react-query' +import { FaMap } from 'react-icons/fa6' +import IconButton from '@mui/material/IconButton' +import Tooltip from '@mui/material/Tooltip' +import uniq from 'lodash/uniq' +import { observer } from 'mobx-react-lite' + +import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' +import { FilterButton } from '../../../shared/MenuBar/FilterButton.jsx' +import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' +import { useProjekteTabs } from '../../../../modules/useProjekteTabs.js' +import { MobxContext } from '../../../../mobxContext.js' + +const iconStyle = { color: 'white' } + +// TODO: how to pass both props? +// TODO: need to add menu to other beobs to enable filtering +export const Menu = memo( + observer(({ apfloraLayer, toggleFilterInput }) => { + const tanstackQueryClient = useQueryClient() + + const store = useContext(MobxContext) + const { setActiveApfloraLayers, activeApfloraLayers } = store + + const [projekteTabs, setProjekteTabs] = useProjekteTabs() + const showMapIfNotYetVisible = useCallback( + (projekteTabs) => { + const isVisible = projekteTabs.includes('karte') + if (!isVisible) { + setProjekteTabs([...projekteTabs, 'karte']) + } + }, + [setProjekteTabs], + ) + const onClickShowOnMap = useCallback(() => { + showMapIfNotYetVisible(projekteTabs) + setActiveApfloraLayers(uniq([...activeApfloraLayers, apfloraLayer])) + }, [ + showMapIfNotYetVisible, + projekteTabs, + setActiveApfloraLayers, + activeApfloraLayers, + ]) + + return ( + <ErrorBoundary> + <MenuBar> + {!!toggleFilterInput && ( + <FilterButton toggleFilterInput={toggleFilterInput} /> + )} + <Tooltip title="Zeige auf Karte"> + <IconButton onClick={onClickShowOnMap}> + <FaMap style={iconStyle} /> + </IconButton> + </Tooltip> + </MenuBar> + </ErrorBoundary> + ) + }), +) diff --git a/src/components/Projekte/Daten/BeobNichtZuzuordnens/index.jsx b/src/components/Projekte/Daten/BeobNichtZuzuordnens/index.jsx new file mode 100644 index 0000000000..a77b4b1cc3 --- /dev/null +++ b/src/components/Projekte/Daten/BeobNichtZuzuordnens/index.jsx @@ -0,0 +1,14 @@ +import { memo } from 'react' +import { useAtom } from 'jotai' + +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { List } from './List.jsx' +import { Component as Ap } from '../Ap/index.jsx' + +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) + + if (isDesktopView) return <Ap /> + + return <List /> +}) diff --git a/src/components/Projekte/Daten/BeobZugeordnets/List.jsx b/src/components/Projekte/Daten/BeobZugeordnets/List.jsx new file mode 100644 index 0000000000..42cf0b873f --- /dev/null +++ b/src/components/Projekte/Daten/BeobZugeordnets/List.jsx @@ -0,0 +1,33 @@ +import { memo, useContext } from 'react' +import { observer } from 'mobx-react-lite' + +import { MobxContext } from '../../../../mobxContext.js' +import { useBeobZugeordnetsNavData } from '../../../../modules/useBeobZugeordnetsNavData.js' +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from '../BeobNichtBeurteilts/Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' + +const menuBarProps = { apfloraLayer: 'beobZugeordnet' } + +export const List = memo( + observer(() => { + const store = useContext(MobxContext) + const { nodeLabelFilter } = store.tree + + const { navData, isLoading, error } = useBeobZugeordnetsNavData() + + if (isLoading) return <Spinner /> + + if (error) return <Error error={error} /> + + return ( + <SharedList + navData={navData} + MenuBarComponent={Menu} + menuBarProps={menuBarProps} + highlightSearchString={nodeLabelFilter.beob} + /> + ) + }), +) diff --git a/src/components/Projekte/Daten/BeobZugeordnets/Menu.jsx b/src/components/Projekte/Daten/BeobZugeordnets/Menu.jsx new file mode 100644 index 0000000000..58f84a0549 --- /dev/null +++ b/src/components/Projekte/Daten/BeobZugeordnets/Menu.jsx @@ -0,0 +1,61 @@ +import { memo, useCallback, useContext } from 'react' +import { useQueryClient } from '@tanstack/react-query' +import { FaMap } from 'react-icons/fa6' +import IconButton from '@mui/material/IconButton' +import Tooltip from '@mui/material/Tooltip' +import uniq from 'lodash/uniq' +import { observer } from 'mobx-react-lite' + +import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' +import { FilterButton } from '../../../shared/MenuBar/FilterButton.jsx' +import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' +import { useProjekteTabs } from '../../../../modules/useProjekteTabs.js' +import { MobxContext } from '../../../../mobxContext.js' + +const iconStyle = { color: 'white' } + +// TODO: how to pass both props? +// TODO: need to add menu to other beobs to enable filtering +export const Menu = memo( + observer(({ apfloraLayer, toggleFilterInput }) => { + const tanstackQueryClient = useQueryClient() + + const store = useContext(MobxContext) + const { setActiveApfloraLayers, activeApfloraLayers } = store + + const [projekteTabs, setProjekteTabs] = useProjekteTabs() + const showMapIfNotYetVisible = useCallback( + (projekteTabs) => { + const isVisible = projekteTabs.includes('karte') + if (!isVisible) { + setProjekteTabs([...projekteTabs, 'karte']) + } + }, + [setProjekteTabs], + ) + const onClickShowOnMap = useCallback(() => { + showMapIfNotYetVisible(projekteTabs) + setActiveApfloraLayers(uniq([...activeApfloraLayers, apfloraLayer])) + }, [ + showMapIfNotYetVisible, + projekteTabs, + setActiveApfloraLayers, + activeApfloraLayers, + ]) + + return ( + <ErrorBoundary> + <MenuBar> + {!!toggleFilterInput && ( + <FilterButton toggleFilterInput={toggleFilterInput} /> + )} + <Tooltip title="Zeige auf Karte"> + <IconButton onClick={onClickShowOnMap}> + <FaMap style={iconStyle} /> + </IconButton> + </Tooltip> + </MenuBar> + </ErrorBoundary> + ) + }), +) diff --git a/src/components/Projekte/Daten/BeobZugeordnets/index.jsx b/src/components/Projekte/Daten/BeobZugeordnets/index.jsx new file mode 100644 index 0000000000..cbbc3bcaf2 --- /dev/null +++ b/src/components/Projekte/Daten/BeobZugeordnets/index.jsx @@ -0,0 +1,14 @@ +import { memo } from 'react' +import { useAtom } from 'jotai' + +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Tpop } from '../Tpop/Tpop.jsx' +import { List } from './List.jsx' + +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) + + if (isDesktopView) return <Tpop /> + + return <List /> +}) diff --git a/src/components/Projekte/Daten/Beobzuordnung/Menu.jsx b/src/components/Projekte/Daten/Beobzuordnung/Menu.jsx index 7a54570973..be0b174ace 100644 --- a/src/components/Projekte/Daten/Beobzuordnung/Menu.jsx +++ b/src/components/Projekte/Daten/Beobzuordnung/Menu.jsx @@ -14,12 +14,12 @@ import { import { MenuBar } from '../../../shared/MenuBar/index.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { showCoordOfBeobOnMapsZhCh } from '../../../../modules/showCoordOfBeobOnMapsZhCh.js' import { showCoordOfBeobOnMapGeoAdminCh } from '../../../../modules/showCoordOfBeobOnMapGeoAdminCh.js' import { copyBeobZugeordnetKoordToTpop } from '../../../../modules/copyBeobZugeordnetKoordToTpop/index.js' import { createNewPopFromBeob } from '../../../../modules/createNewPopFromBeob/index.js' -import { StyledLoadingButton, StyledButton } from '../TpopRouter/Menu.jsx' +import { StyledLoadingButton, StyledButton } from '../Tpop/Menu.jsx' export const Menu = memo( observer(() => { @@ -28,7 +28,7 @@ export const Menu = memo( const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { projId, apId, beobId, tpopId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const isBeobZugeordnet = !!tpopId const isBeobNichtBeurteilt = diff --git a/src/components/Projekte/Daten/Beobzuordnung/index.jsx b/src/components/Projekte/Daten/Beobzuordnung/index.jsx index 8b6d82b63b..5d01aa7bee 100644 --- a/src/components/Projekte/Daten/Beobzuordnung/index.jsx +++ b/src/components/Projekte/Daten/Beobzuordnung/index.jsx @@ -21,7 +21,7 @@ import { saveNichtZuordnenToDb } from './saveNichtZuordnenToDb.js' import { saveArtIdToDb } from './saveArtIdToDb.js' import { saveTpopIdToDb } from './saveTpopIdToDb.js' import { sendMail } from '../../../../modules/sendMail.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { Error } from '../../../shared/Error.jsx' import { Spinner } from '../../../shared/Spinner.jsx' @@ -142,7 +142,7 @@ export const Component = memo( : 'uups' const client = useApolloClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { data, loading, error, refetch } = useQuery(query, { variables: { @@ -304,7 +304,7 @@ export const Component = memo( <Container> <FormTitle title="Beobachtung" - menuBar={<Menu />} + MenuBarComponent={Menu} /> <FormContainer> <FieldsContainer> diff --git a/src/components/Projekte/Daten/Beobzuordnungs/index.jsx b/src/components/Projekte/Daten/Beobzuordnungs/index.jsx deleted file mode 100644 index 5aabd42928..0000000000 --- a/src/components/Projekte/Daten/Beobzuordnungs/index.jsx +++ /dev/null @@ -1,70 +0,0 @@ -import { memo, useContext } from 'react' -import { useApolloClient, gql } from '@apollo/client' -import { useQuery } from '@tanstack/react-query' -import { observer } from 'mobx-react-lite' -import { useParams, useLocation } from 'react-router' - -import { StoreContext } from '../../../../storeContext.js' -import { createApsQuery } from '../../../../modules/createApsQuery.js' -import { createBeobsQuery } from '../../../../modules/createBeobsQuery.js' -import { List } from '../../../shared/List/index.jsx' -import { Menu } from './Menu.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Error } from '../../../shared/Error.jsx' - -export const Component = memo( - observer(() => { - const { apId, tpopId } = useParams() - const apolloClient = useApolloClient() - const store = useContext(StoreContext) - const { beobGqlFilterForTree, nodeLabelFilter } = store.tree - const { pathname } = useLocation() - - const isBeobzugeordnet = !!tpopId && pathname.includes('Beobachtungen') - const isBeobNichtBeurteilt = - !tpopId && pathname.includes('nicht-beurteilte-Beobachtungen') - const isBeobNichtZuzuordnen = - !tpopId && pathname.includes('nicht-zuzuordnende-Beobachtungen') - const type = - isBeobzugeordnet ? 'zugeordnet' - : isBeobNichtBeurteilt ? 'nichtBeurteilt' - : isBeobNichtZuzuordnen ? 'nichtZuzuordnen' - : 'noType' - const apfloraLayer = - type === 'zugeordnet' ? 'beobZugeordnet' - : type === 'nichtBeurteilt' ? 'beobNichtBeurteilt' - : type === 'nichtZuzuordnen' ? 'beobNichtZuzuordnen' - : 'beobNichtZuzuordnen' - const title = - type === 'zugeordnet' ? 'Beobachtungen zugeordnet' - : type === 'nichtBeurteilt' ? 'Beobachtungen nicht beurteilt' - : type === 'nichtZuzuordnen' ? 'Beobachtungen nicht zuzuordnen' - : 'Beobachtungen' - - const { data, isLoading, error } = useQuery( - createBeobsQuery({ - tpopId, - apId: tpopId ? undefined : apId, - beobGqlFilterForTree, - apolloClient, - type, - }), - ) - const beobs = data?.data?.allBeobs?.nodes ?? [] - const totalCount = data?.data?.beobsCount?.totalCount ?? 0 - - if (isLoading) return <Spinner /> - - if (error) return <Error error={error} /> - - return ( - <List - items={beobs} - title={title} - totalCount={totalCount} - menuBar={<Menu apfloraLayer={apfloraLayer} />} - highlightSearchString={nodeLabelFilter.beob} - /> - ) - }), -) diff --git a/src/components/Projekte/Daten/CurrentIssues/List.jsx b/src/components/Projekte/Daten/CurrentIssues/List.jsx new file mode 100644 index 0000000000..61c595189d --- /dev/null +++ b/src/components/Projekte/Daten/CurrentIssues/List.jsx @@ -0,0 +1,18 @@ +import { memo } from 'react' + +import { useCurrentissuesNavData } from '../../../../modules/useCurrentissuesNavData.js' +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' + +export const List = memo(() => { + const { navData, isLoading, error } = useCurrentissuesNavData() + const currentissues = navData?.data?.allCurrentissues?.nodes ?? [] + const totalCount = navData?.data?.allCurrentissues?.totalCount ?? 0 + + if (isLoading) return <Spinner /> + + if (error) return <Error error={error} /> + + return <SharedList navData={navData} /> +}) diff --git a/src/components/Projekte/Daten/CurrentIssues/index.jsx b/src/components/Projekte/Daten/CurrentIssues/index.jsx index e5f8932de6..caab62a1ea 100644 --- a/src/components/Projekte/Daten/CurrentIssues/index.jsx +++ b/src/components/Projekte/Daten/CurrentIssues/index.jsx @@ -1,32 +1,13 @@ import { memo } from 'react' -import { useApolloClient } from '@apollo/client' -import { useQuery } from '@tanstack/react-query' +import { useAtom } from 'jotai' -import { createCurrentissuesQuery } from '../../../../modules/createCurrentissuesQuery.js' -import { List } from '../../../shared/List/index.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Error } from '../../../shared/Error.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { List } from './List.jsx' export const Component = memo(() => { - const apolloClient = useApolloClient() + const [isDesktopView] = useAtom(isDesktopViewAtom) - const { data, isLoading, error } = useQuery( - createCurrentissuesQuery({ - apolloClient, - }), - ) - const currentissues = data?.data?.allCurrentissues?.nodes ?? [] - const totalCount = data?.data?.totalCount?.totalCount ?? 0 + if (isDesktopView) return null - if (isLoading) return <Spinner /> - - if (error) return <Error error={error} /> - - return ( - <List - items={currentissues} - title="Aktuelle Fehler" - totalCount={totalCount} - /> - ) + return <List /> }) diff --git a/src/components/Projekte/Daten/EkAbrechnungstypWertes/List.jsx b/src/components/Projekte/Daten/EkAbrechnungstypWertes/List.jsx new file mode 100644 index 0000000000..fd0e9ba62e --- /dev/null +++ b/src/components/Projekte/Daten/EkAbrechnungstypWertes/List.jsx @@ -0,0 +1,30 @@ +import { memo, useContext } from 'react' +import { observer } from 'mobx-react-lite' + +import { MobxContext } from '../../../../mobxContext.js' +import { useEkAbrechnungstypWertesNavData } from '../../../../modules/useEkAbrechnungstypWertesNavData.js' +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from './Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' + +export const List = memo( + observer(() => { + const store = useContext(MobxContext) + const { nodeLabelFilter } = store.tree + + const { navData, isLoading, error } = useEkAbrechnungstypWertesNavData() + + if (isLoading) return <Spinner /> + + if (error) return <Error error={error} /> + + return ( + <SharedList + navData={navData} + MenuBarComponent={Menu} + highlightSearchString={nodeLabelFilter.ekAbrechnungstypWerte} + /> + ) + }), +) diff --git a/src/components/Projekte/Daten/EkAbrechnungstypWertes/Menu.jsx b/src/components/Projekte/Daten/EkAbrechnungstypWertes/Menu.jsx index 9cf7bfb632..aeaf7addeb 100644 --- a/src/components/Projekte/Daten/EkAbrechnungstypWertes/Menu.jsx +++ b/src/components/Projekte/Daten/EkAbrechnungstypWertes/Menu.jsx @@ -10,17 +10,15 @@ import IconButton from '@mui/material/IconButton' import Tooltip from '@mui/material/Tooltip' import styled from '@emotion/styled' import { observer } from 'mobx-react-lite' -import { useAtom } from 'jotai' import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' +import { FilterButton } from '../../../shared/MenuBar/FilterButton.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' import { moveTo } from '../../../../modules/moveTo/index.js' import { copyTo } from '../../../../modules/copyTo/index.js' import { closeLowerNodes } from '../../TreeContainer/closeLowerNodes.js' -import { StoreContext } from '../../../../storeContext.js' -import { LabelFilter, labelFilterWidth } from '../../../shared/LabelFilter.jsx' -import { listLabelFilterIsIconAtom } from '../../../../JotaiStore/index.js' +import { MobxContext } from '../../../../mobxContext.js' const Fitter = styled.div` margin-top: -15px; @@ -29,13 +27,13 @@ const Fitter = styled.div` const iconStyle = { color: 'white' } export const Menu = memo( - observer(() => { + observer(({ toggleFilterInput }) => { const { search, pathname } = useLocation() const navigate = useNavigate() const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { projId, ekAbrechnungstypWerteId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result @@ -72,14 +70,12 @@ export const Menu = memo( navigate(`./${id}${search}`) }, [client, store, tanstackQueryClient, navigate, search]) - const [labelFilterIsIcon] = useAtom(listLabelFilterIsIconAtom) - return ( <ErrorBoundary> <MenuBar> - <LabelFilter - width={labelFilterIsIcon ? buttonWidth : labelFilterWidth} - /> + {!!toggleFilterInput && ( + <FilterButton toggleFilterInput={toggleFilterInput} /> + )} <Tooltip title="Neuen Abrechnungstyp erstellen"> <IconButton onClick={onClickAdd}> <FaPlus style={iconStyle} /> diff --git a/src/components/Projekte/Daten/EkAbrechnungstypWertes/index.jsx b/src/components/Projekte/Daten/EkAbrechnungstypWertes/index.jsx index 373760ef6a..caab62a1ea 100644 --- a/src/components/Projekte/Daten/EkAbrechnungstypWertes/index.jsx +++ b/src/components/Projekte/Daten/EkAbrechnungstypWertes/index.jsx @@ -1,44 +1,13 @@ -import { memo, useContext } from 'react' -import { useApolloClient, gql } from '@apollo/client' -import { useQuery } from '@tanstack/react-query' -import { observer } from 'mobx-react-lite' +import { memo } from 'react' +import { useAtom } from 'jotai' -import { StoreContext } from '../../../../storeContext.js' -import { createEkAbrechnungstypWertesQuery } from '../../../../modules/createEkAbrechnungstypWertesQuery.js' -import { List } from '../../../shared/List/index.jsx' -import { Menu } from './Menu.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Error } from '../../../shared/Error.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { List } from './List.jsx' -export const Component = memo( - observer(() => { - const apolloClient = useApolloClient() - const store = useContext(StoreContext) - const { ekAbrechnungstypWerteGqlFilterForTree, nodeLabelFilter } = - store.tree +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) - const { data, isLoading, error } = useQuery( - createEkAbrechnungstypWertesQuery({ - ekAbrechnungstypWerteGqlFilterForTree, - apolloClient, - }), - ) - const ekAbrechnungstypWertes = - data?.data?.allEkAbrechnungstypWertes?.nodes ?? [] - const totalCount = data?.data?.totalCount?.totalCount ?? 0 + if (isDesktopView) return null - if (isLoading) return <Spinner /> - - if (error) return <Error error={error} /> - - return ( - <List - items={ekAbrechnungstypWertes} - title="Teil-Population: EK-Abrechnungstypen" - totalCount={totalCount} - menuBar={<Menu />} - highlightSearchString={nodeLabelFilter.ekAbrechnungstypWerte} - /> - ) - }), -) + return <List /> +}) diff --git a/src/components/Projekte/Daten/Ekfrequenz/Menu.jsx b/src/components/Projekte/Daten/Ekfrequenz/Menu.jsx index 350ff53f0d..66b1747ac2 100644 --- a/src/components/Projekte/Daten/Ekfrequenz/Menu.jsx +++ b/src/components/Projekte/Daten/Ekfrequenz/Menu.jsx @@ -13,7 +13,7 @@ import isEqual from 'lodash/isEqual' import { MenuBar } from '../../../shared/MenuBar/index.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' const iconStyle = { color: 'white' } @@ -25,7 +25,7 @@ export const Menu = memo( const client = useApolloClient() const queryClient = useQueryClient() const { projId, apId, ekfrequenzId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result @@ -57,6 +57,9 @@ export const Menu = memo( queryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + queryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) const id = result?.data?.createEkfrequenz?.ekfrequenz?.id navigate( `/Daten/Projekte/${projId}/Arten/${apId}/EK-Frequenzen/${id}${search}`, @@ -104,6 +107,9 @@ export const Menu = memo( queryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + queryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) // navigate to parent navigate(`/Daten/Projekte/${projId}/Arten/${apId}/EK-Frequenzen${search}`) }, [ diff --git a/src/components/Projekte/Daten/Ekfrequenz/index.jsx b/src/components/Projekte/Daten/Ekfrequenz/index.jsx index 40d2a4872e..62fe412c25 100644 --- a/src/components/Projekte/Daten/Ekfrequenz/index.jsx +++ b/src/components/Projekte/Daten/Ekfrequenz/index.jsx @@ -11,7 +11,7 @@ import { Kontrolljahre } from './Kontrolljahre.jsx' import { FormTitle } from '../../../shared/FormTitle/index.jsx' import { query } from './query.js' import { queryEkAbrechnungstypWertes } from './queryEkAbrechnungstypWertes.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' import { ekfrequenz } from '../../../shared/fragments.js' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' @@ -80,7 +80,7 @@ export const Component = memo( const queryClient = useQueryClient() const client = useApolloClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const [fieldErrors, setFieldErrors] = useState({}) @@ -160,7 +160,7 @@ export const Component = memo( <Container> <FormTitle title="EK-Frequenz" - menuBar={<Menu />} + MenuBarComponent={Menu} /> <FormContainer> <TextField diff --git a/src/components/Projekte/Daten/Ekfrequenzs/List.jsx b/src/components/Projekte/Daten/Ekfrequenzs/List.jsx new file mode 100644 index 0000000000..079ec02a1e --- /dev/null +++ b/src/components/Projekte/Daten/Ekfrequenzs/List.jsx @@ -0,0 +1,30 @@ +import { memo, useContext } from 'react' +import { observer } from 'mobx-react-lite' + +import { MobxContext } from '../../../../mobxContext.js' +import { useEkfrequenzsNavData } from '../../../../modules/useEkfrequenzsNavData.js' +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from './Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' + +export const List = memo( + observer(() => { + const store = useContext(MobxContext) + const { nodeLabelFilter } = store.tree + + const { navData, isLoading, error } = useEkfrequenzsNavData() + + if (isLoading) return <Spinner /> + + if (error) return <Error error={error} /> + + return ( + <SharedList + navData={navData} + MenuBarComponent={Menu} + highlightSearchString={nodeLabelFilter.ekfrequenz} + /> + ) + }), +) diff --git a/src/components/Projekte/Daten/Ekfrequenzs/Menu.jsx b/src/components/Projekte/Daten/Ekfrequenzs/Menu.jsx index e0c7d7cc7e..a7a02e4dd6 100644 --- a/src/components/Projekte/Daten/Ekfrequenzs/Menu.jsx +++ b/src/components/Projekte/Daten/Ekfrequenzs/Menu.jsx @@ -7,26 +7,24 @@ import { MdContentCopy } from 'react-icons/md' import IconButton from '@mui/material/IconButton' import Tooltip from '@mui/material/Tooltip' import { observer } from 'mobx-react-lite' -import { useAtom } from 'jotai' import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' +import { FilterButton } from '../../../shared/MenuBar/FilterButton.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' -import { LabelFilter, labelFilterWidth } from '../../../shared/LabelFilter.jsx' -import { listLabelFilterIsIconAtom } from '../../../../JotaiStore/index.js' +import { MobxContext } from '../../../../mobxContext.js' const iconStyle = { color: 'white' } // TODO: add menu to setOpenChooseApToCopyEkfrequenzsFrom export const Menu = memo( - observer(() => { + observer(({ toggleFilterInput }) => { const { search } = useLocation() const navigate = useNavigate() const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { apId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { setOpenChooseApToCopyEkfrequenzsFrom } = store const onClickAdd = useCallback(async () => { @@ -59,6 +57,9 @@ export const Menu = memo( tanstackQueryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + tanstackQueryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) const id = result?.data?.createEkfrequenz?.ekfrequenz?.id navigate(`./${id}${search}`) }, [client, store, tanstackQueryClient, navigate, search, apId]) @@ -68,14 +69,12 @@ export const Menu = memo( [setOpenChooseApToCopyEkfrequenzsFrom], ) - const [labelFilterIsIcon] = useAtom(listLabelFilterIsIconAtom) - return ( <ErrorBoundary> <MenuBar> - <LabelFilter - width={labelFilterIsIcon ? buttonWidth : labelFilterWidth} - /> + {!!toggleFilterInput && ( + <FilterButton toggleFilterInput={toggleFilterInput} /> + )} <Tooltip title="Neue EK-Frequenz erstellen"> <IconButton onClick={onClickAdd}> <FaPlus style={iconStyle} /> diff --git a/src/components/Projekte/Daten/Ekfrequenzs/index.jsx b/src/components/Projekte/Daten/Ekfrequenzs/index.jsx index 2213283109..ea4a216067 100644 --- a/src/components/Projekte/Daten/Ekfrequenzs/index.jsx +++ b/src/components/Projekte/Daten/Ekfrequenzs/index.jsx @@ -1,48 +1,14 @@ -import { memo, useContext } from 'react' -import { useApolloClient, gql } from '@apollo/client' -import { useQuery } from '@tanstack/react-query' -import { observer } from 'mobx-react-lite' -import { useParams } from 'react-router' +import { memo } from 'react' +import { useAtom } from 'jotai' -import { StoreContext } from '../../../../storeContext.js' -import { createApsQuery } from '../../../../modules/createApsQuery.js' -import { createEkfrequenzsQuery } from '../../../../modules/createEkfrequenzsQuery.js' -import { List } from '../../../shared/List/index.jsx' -import { Menu } from './Menu.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Error } from '../../../shared/Error.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Ap } from '../Ap/index.jsx' +import { List } from './List.jsx' -export const Component = memo( - observer(() => { - const { apId } = useParams() - const apolloClient = useApolloClient() - const store = useContext(StoreContext) - const { ekfrequenzGqlFilterForTree, nodeLabelFilter } = store.tree +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) - const { data, isLoading, error } = useQuery( - createEkfrequenzsQuery({ - apId, - ekfrequenzGqlFilterForTree, - apolloClient, - }), - ) - const ekfrequenzs = - data?.data?.apById?.ekfrequenzsByApId?.nodes ?? [] - const totalCount = - data?.data?.apById?.ekfrequenzsCount?.totalCount ?? 0 + if (isDesktopView) return <Ap /> - if (isLoading) return <Spinner /> - - if (error) return <Error error={error} /> - - return ( - <List - items={ekfrequenzs} - title="EK-Frequenzen" - totalCount={totalCount} - menuBar={<Menu />} - highlightSearchString={nodeLabelFilter.ekfrequenz} - /> - ) - }), -) + return <List /> +}) diff --git a/src/components/Projekte/Daten/Ekzaehleinheit/Menu.jsx b/src/components/Projekte/Daten/Ekzaehleinheit/Menu.jsx index c0657883e6..06fb78b3cb 100644 --- a/src/components/Projekte/Daten/Ekzaehleinheit/Menu.jsx +++ b/src/components/Projekte/Daten/Ekzaehleinheit/Menu.jsx @@ -13,7 +13,7 @@ import isEqual from 'lodash/isEqual' import { MenuBar } from '../../../shared/MenuBar/index.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' const iconStyle = { color: 'white' } @@ -25,7 +25,7 @@ export const Menu = memo( const client = useApolloClient() const queryClient = useQueryClient() const { projId, apId, zaehleinheitId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result @@ -57,6 +57,9 @@ export const Menu = memo( queryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + queryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) const id = result?.data?.createEkzaehleinheit?.ekzaehleinheit?.id navigate( `/Daten/Projekte/${projId}/Arten/${apId}/EK-Zähleinheiten/${id}${search}`, @@ -104,6 +107,9 @@ export const Menu = memo( queryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + queryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) // navigate to parent navigate( `/Daten/Projekte/${projId}/Arten/${apId}/EK-Zähleinheiten${search}`, diff --git a/src/components/Projekte/Daten/Ekzaehleinheit/index.jsx b/src/components/Projekte/Daten/Ekzaehleinheit/index.jsx index eded95030c..a885fb7595 100644 --- a/src/components/Projekte/Daten/Ekzaehleinheit/index.jsx +++ b/src/components/Projekte/Daten/Ekzaehleinheit/index.jsx @@ -11,7 +11,7 @@ import { Checkbox2States } from '../../../shared/Checkbox2States.jsx' import { FormTitle } from '../../../shared/FormTitle/index.jsx' import { query } from './query.js' import { queryLists } from './queryLists.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { Error } from '../../../shared/Error.jsx' @@ -53,7 +53,7 @@ export const Component = memo( const queryClient = useQueryClient() const client = useApolloClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const [fieldErrors, setFieldErrors] = useState({}) @@ -166,7 +166,7 @@ export const Component = memo( <Container> <FormTitle title="EK-Zähleinheit" - menuBar={<Menu />} + MenuBarComponent={Menu} /> <FormContainer> <Select diff --git a/src/components/Projekte/Daten/Ekzaehleinheits/List.jsx b/src/components/Projekte/Daten/Ekzaehleinheits/List.jsx new file mode 100644 index 0000000000..63f2c034eb --- /dev/null +++ b/src/components/Projekte/Daten/Ekzaehleinheits/List.jsx @@ -0,0 +1,30 @@ +import { memo, useContext } from 'react' +import { observer } from 'mobx-react-lite' + +import { MobxContext } from '../../../../mobxContext.js' +import { useEkzaehleinheitsNavData } from '../../../../modules/useEkzaehleinheitsNavData.js' +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from './Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' + +export const List = memo( + observer(() => { + const store = useContext(MobxContext) + const { nodeLabelFilter } = store.tree + + const { navData, isLoading, error } = useEkzaehleinheitsNavData() + + if (isLoading) return <Spinner /> + + if (error) return <Error error={error} /> + + return ( + <SharedList + navData={navData} + MenuBarComponent={Menu} + highlightSearchString={nodeLabelFilter.ekzaehleinheit} + /> + ) + }), +) diff --git a/src/components/Projekte/Daten/Ekzaehleinheits/Menu.jsx b/src/components/Projekte/Daten/Ekzaehleinheits/Menu.jsx index 5f04a37a4f..e814c93401 100644 --- a/src/components/Projekte/Daten/Ekzaehleinheits/Menu.jsx +++ b/src/components/Projekte/Daten/Ekzaehleinheits/Menu.jsx @@ -6,24 +6,21 @@ import { FaPlus } from 'react-icons/fa6' import IconButton from '@mui/material/IconButton' import Tooltip from '@mui/material/Tooltip' import { observer } from 'mobx-react-lite' -import { useAtom } from 'jotai' import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' +import { FilterButton } from '../../../shared/MenuBar/FilterButton.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' -import { LabelFilter, labelFilterWidth } from '../../../shared/LabelFilter.jsx' -import { listLabelFilterIsIconAtom } from '../../../../JotaiStore/index.js' - +import { MobxContext } from '../../../../mobxContext.js' const iconStyle = { color: 'white' } export const Menu = memo( - observer(() => { + observer(({ toggleFilterInput }) => { const { search } = useLocation() const navigate = useNavigate() const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { apId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result @@ -55,18 +52,19 @@ export const Menu = memo( tanstackQueryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + tanstackQueryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) const id = result?.data?.createEkzaehleinheit?.ekzaehleinheit?.id navigate(`./${id}${search}`) }, [client, store, tanstackQueryClient, navigate, search, apId]) - const [labelFilterIsIcon] = useAtom(listLabelFilterIsIconAtom) - return ( <ErrorBoundary> <MenuBar> - <LabelFilter - width={labelFilterIsIcon ? buttonWidth : labelFilterWidth} - /> + {!!toggleFilterInput && ( + <FilterButton toggleFilterInput={toggleFilterInput} /> + )} <Tooltip title="Neue EK-Zähleinheiten erstellen"> <IconButton onClick={onClickAdd}> <FaPlus style={iconStyle} /> diff --git a/src/components/Projekte/Daten/Ekzaehleinheits/index.jsx b/src/components/Projekte/Daten/Ekzaehleinheits/index.jsx index 495355fb57..ea4a216067 100644 --- a/src/components/Projekte/Daten/Ekzaehleinheits/index.jsx +++ b/src/components/Projekte/Daten/Ekzaehleinheits/index.jsx @@ -1,48 +1,14 @@ -import { memo, useContext } from 'react' -import { useApolloClient, gql } from '@apollo/client' -import { useQuery } from '@tanstack/react-query' -import { observer } from 'mobx-react-lite' -import { useParams } from 'react-router' +import { memo } from 'react' +import { useAtom } from 'jotai' -import { StoreContext } from '../../../../storeContext.js' -import { createApsQuery } from '../../../../modules/createApsQuery.js' -import { createEkzaehleinheitsQuery } from '../../../../modules/createEkzaehleinheitsQuery.js' -import { List } from '../../../shared/List/index.jsx' -import { Menu } from './Menu.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Error } from '../../../shared/Error.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Ap } from '../Ap/index.jsx' +import { List } from './List.jsx' -export const Component = memo( - observer(() => { - const { apId } = useParams() - const apolloClient = useApolloClient() - const store = useContext(StoreContext) - const { ekzaehleinheitGqlFilterForTree, nodeLabelFilter } = store.tree +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) - const { data, isLoading, error } = useQuery( - createEkzaehleinheitsQuery({ - apId, - ekzaehleinheitGqlFilterForTree, - apolloClient, - }), - ) - const ekzaehleinheits = - data?.data?.apById?.ekzaehleinheitsByApId?.nodes ?? [] - const totalCount = - data?.data?.apById?.ekzaehleinheitsCount?.totalCount ?? 0 + if (isDesktopView) return <Ap /> - if (isLoading) return <Spinner /> - - if (error) return <Error error={error} /> - - return ( - <List - items={ekzaehleinheits} - title="EK-Zähleinheiten" - totalCount={totalCount} - menuBar={<Menu />} - highlightSearchString={nodeLabelFilter.ekzaehleinheit} - /> - ) - }), -) + return <List /> +}) diff --git a/src/components/Projekte/Daten/Erfkrit/Menu.jsx b/src/components/Projekte/Daten/Erfkrit/Menu.jsx index 0d549e3140..0a59509b1c 100644 --- a/src/components/Projekte/Daten/Erfkrit/Menu.jsx +++ b/src/components/Projekte/Daten/Erfkrit/Menu.jsx @@ -13,7 +13,7 @@ import isEqual from 'lodash/isEqual' import { MenuBar } from '../../../shared/MenuBar/index.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' const iconStyle = { color: 'white' } @@ -24,8 +24,8 @@ export const Menu = memo( const navigate = useNavigate() const client = useApolloClient() const queryClient = useQueryClient() - const { projId, apId } = useParams() - const store = useContext(StoreContext) + const { projId, apId, erfkritId } = useParams() + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result @@ -57,6 +57,9 @@ export const Menu = memo( queryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + queryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) const id = result?.data?.createErfkrit?.erfkrit?.id navigate( `/Daten/Projekte/${projId}/Arten/${apId}/AP-Erfolgskriterien/${id}${search}`, @@ -104,6 +107,9 @@ export const Menu = memo( queryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + queryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) // navigate to parent navigate( `/Daten/Projekte/${projId}/Arten/${apId}/AP-Erfolgskriterien${search}`, diff --git a/src/components/Projekte/Daten/Erfkrit/index.jsx b/src/components/Projekte/Daten/Erfkrit/index.jsx index 9fa377e6e6..ab38c56c86 100644 --- a/src/components/Projekte/Daten/Erfkrit/index.jsx +++ b/src/components/Projekte/Daten/Erfkrit/index.jsx @@ -10,7 +10,7 @@ import { TextField } from '../../../shared/TextField.jsx' import { FormTitle } from '../../../shared/FormTitle/index.jsx' import { query } from './query.js' import { queryLists } from './queryLists.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { Error } from '../../../shared/Error.jsx' @@ -46,7 +46,7 @@ export const Component = memo( const queryClient = useQueryClient() const client = useApolloClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const [fieldErrors, setFieldErrors] = useState({}) @@ -124,7 +124,7 @@ export const Component = memo( <Container> <FormTitle title="Erfolgs-Kriterium" - menuBar={<Menu />} + MenuBarComponent={Menu} /> <FormContainer> <RadioButtonGroup diff --git a/src/components/Projekte/Daten/Erfkrits/List.jsx b/src/components/Projekte/Daten/Erfkrits/List.jsx new file mode 100644 index 0000000000..e4bf76c226 --- /dev/null +++ b/src/components/Projekte/Daten/Erfkrits/List.jsx @@ -0,0 +1,30 @@ +import { memo, useContext } from 'react' +import { observer } from 'mobx-react-lite' + +import { MobxContext } from '../../../../mobxContext.js' +import { useErfkritsNavData } from '../../../../modules/useErfkritsNavData.js' +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from './Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' + +export const List = memo( + observer(() => { + const store = useContext(MobxContext) + const { nodeLabelFilter } = store.tree + + const { navData, isLoading, error } = useErfkritsNavData() + + if (isLoading) return <Spinner /> + + if (error) return <Error error={error} /> + + return ( + <SharedList + navData={navData} + MenuBarComponent={Menu} + highlightSearchString={nodeLabelFilter.erfkrit} + /> + ) + }), +) diff --git a/src/components/Projekte/Daten/Erfkrits/Menu.jsx b/src/components/Projekte/Daten/Erfkrits/Menu.jsx index d5bc88d762..fdbe044830 100644 --- a/src/components/Projekte/Daten/Erfkrits/Menu.jsx +++ b/src/components/Projekte/Daten/Erfkrits/Menu.jsx @@ -7,25 +7,23 @@ import { MdContentCopy } from 'react-icons/md' import IconButton from '@mui/material/IconButton' import Tooltip from '@mui/material/Tooltip' import { observer } from 'mobx-react-lite' -import { useAtom } from 'jotai' import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' +import { FilterButton } from '../../../shared/MenuBar/FilterButton.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' -import { LabelFilter, labelFilterWidth } from '../../../shared/LabelFilter.jsx' -import { listLabelFilterIsIconAtom } from '../../../../JotaiStore/index.js' +import { MobxContext } from '../../../../mobxContext.js' const iconStyle = { color: 'white' } export const Menu = memo( - observer(() => { + observer(({ toggleFilterInput }) => { const { search } = useLocation() const navigate = useNavigate() const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { apId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { setOpenChooseApToCopyErfkritsFrom } = store const onClickAdd = useCallback(async () => { @@ -58,6 +56,9 @@ export const Menu = memo( tanstackQueryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + tanstackQueryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) const id = result?.data?.createErfkrit?.erfkrit?.id navigate(`./${id}${search}`) }, [client, store, tanstackQueryClient, navigate, search, apId]) @@ -67,14 +68,12 @@ export const Menu = memo( [setOpenChooseApToCopyErfkritsFrom], ) - const [labelFilterIsIcon] = useAtom(listLabelFilterIsIconAtom) - return ( <ErrorBoundary> <MenuBar> - <LabelFilter - width={labelFilterIsIcon ? buttonWidth : labelFilterWidth} - /> + {!!toggleFilterInput && ( + <FilterButton toggleFilterInput={toggleFilterInput} /> + )} <Tooltip title="Neues Erfolgs-Kriterium erstellen"> <IconButton onClick={onClickAdd}> <FaPlus style={iconStyle} /> diff --git a/src/components/Projekte/Daten/Erfkrits/index.jsx b/src/components/Projekte/Daten/Erfkrits/index.jsx index c44bb065e7..ea4a216067 100644 --- a/src/components/Projekte/Daten/Erfkrits/index.jsx +++ b/src/components/Projekte/Daten/Erfkrits/index.jsx @@ -1,46 +1,14 @@ -import { memo, useContext } from 'react' -import { useApolloClient, gql } from '@apollo/client' -import { useQuery } from '@tanstack/react-query' -import { observer } from 'mobx-react-lite' -import { useParams } from 'react-router' +import { memo } from 'react' +import { useAtom } from 'jotai' -import { StoreContext } from '../../../../storeContext.js' -import { createApsQuery } from '../../../../modules/createApsQuery.js' -import { createErfkritsQuery } from '../../../../modules/createErfkritsQuery.js' -import { List } from '../../../shared/List/index.jsx' -import { Menu } from './Menu.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Error } from '../../../shared/Error.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Ap } from '../Ap/index.jsx' +import { List } from './List.jsx' -export const Component = memo( - observer(() => { - const { apId } = useParams() - const apolloClient = useApolloClient() - const store = useContext(StoreContext) - const { erfkritGqlFilterForTree, nodeLabelFilter } = store.tree +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) - const { data, isLoading, error } = useQuery( - createErfkritsQuery({ - apId, - erfkritGqlFilterForTree, - apolloClient, - }), - ) - const erfkrits = data?.data?.apById?.erfkritsByApId?.nodes ?? [] - const totalCount = data?.data?.apById?.erfkritsCount?.totalCount ?? 0 + if (isDesktopView) return <Ap /> - if (isLoading) return <Spinner /> - - if (error) return <Error error={error} /> - - return ( - <List - items={erfkrits} - title="AP-Erfolgskriterien" - totalCount={totalCount} - menuBar={<Menu />} - highlightSearchString={nodeLabelFilter.erfkrit} - /> - ) - }), -) + return <List /> +}) diff --git a/src/components/Projekte/Daten/Idealbiotop/Dateien.jsx b/src/components/Projekte/Daten/Idealbiotop/Dateien.jsx index 6ceedaa3a0..a148be23de 100644 --- a/src/components/Projekte/Daten/Idealbiotop/Dateien.jsx +++ b/src/components/Projekte/Daten/Idealbiotop/Dateien.jsx @@ -4,6 +4,7 @@ import { useApolloClient, useQuery } from '@apollo/client' import { FilesRouter } from '../../../shared/Files/index.jsx' import { query } from './query.js' +import { FormTitle } from '../../../shared/FormTitle/index.jsx' export const Component = memo(() => { const { apId } = useParams() @@ -21,9 +22,12 @@ export const Component = memo(() => { ) return ( - <FilesRouter - parentId={row.id} - parent="idealbiotop" - /> + <> + <FormTitle title="Dateien" /> + <FilesRouter + parentId={row.id} + parent="idealbiotop" + /> + </> ) }) diff --git a/src/components/Projekte/Daten/Idealbiotop/Idealbiotop.jsx b/src/components/Projekte/Daten/Idealbiotop/Idealbiotop.jsx new file mode 100644 index 0000000000..f57d7a3c33 --- /dev/null +++ b/src/components/Projekte/Daten/Idealbiotop/Idealbiotop.jsx @@ -0,0 +1,296 @@ +import { memo, useState, useCallback, useContext, useMemo } from 'react' +import styled from '@emotion/styled' +import { observer } from 'mobx-react-lite' +import { useApolloClient, useQuery, gql } from '@apollo/client' +import { Form, useParams } from 'react-router' + +import { TextField } from '../../../shared/TextField.jsx' +import { DateField } from '../../../shared/Date.jsx' +import { constants } from '../../../../modules/constants.js' +import { query } from './query.js' +import { MobxContext } from '../../../../mobxContext.js' +import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' +import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' +import { Error } from '../../../shared/Error.jsx' +import { idealbiotop } from '../../../shared/fragments.js' +import { Spinner } from '../../../shared/Spinner.jsx' +import { FormTitle } from '../../../shared/FormTitle/index.jsx' + +const FormContainer = styled.div` + padding: 10px; + height: 100%; + column-width: ${constants.columnWidth}px; +` +const Section = styled.div` + padding-top: 20px; + padding-bottom: 7px; + font-weight: bold; + break-after: avoid; + &:after { + content: ':'; + } +` +const simplebarStyle = { maxHeight: '100%', height: '100%' } + +const fieldTypes = { + apId: 'UUID', + erstelldatum: 'Date', + hoehenlage: 'String', + region: 'String', + exposition: 'String', + besonnung: 'String', + hangneigung: 'String', + bodenTyp: 'String', + bodenKalkgehalt: 'String', + bodenDurchlaessigkeit: 'String', + bodenHumus: 'String', + bodenNaehrstoffgehalt: 'String', + wasserhaushalt: 'String', + konkurrenz: 'String', + moosschicht: 'String', + krautschicht: 'String', + strauchschicht: 'String', + baumschicht: 'String', + bemerkungen: 'String', +} + +export const Component = memo( + observer(() => { + const { apId } = useParams() + + const store = useContext(MobxContext) + const client = useApolloClient() + + const [fieldErrors, setFieldErrors] = useState({}) + + const { data, loading, error } = useQuery(query, { + variables: { + id: apId, + }, + }) + + const row = useMemo( + () => data?.allIdealbiotops?.nodes?.[0] ?? {}, + [data?.allIdealbiotops?.nodes], + ) + + const saveToDb = useCallback( + async (event) => { + const field = event.target.name + const value = ifIsNumericAsNumber(event.target.value) + + const variables = { + id: apId, + [field]: value, + changedBy: store.user.name, + } + try { + await client.mutate({ + mutation: gql` + mutation updateIdealbiotop( + $id: UUID! + $${field}: ${fieldTypes[field]} + $changedBy: String + ) { + updateIdealbiotopById( + input: { + id: $id + idealbiotopPatch: { + ${field}: $${field} + changedBy: $changedBy + } + } + ) { + idealbiotop { + ...IdealbiotopFields + } + } + } + ${idealbiotop} + `, + variables, + }) + } catch (error) { + return setFieldErrors({ [field]: error.message }) + } + setFieldErrors({}) + }, + [client, row, store.user.name], + ) + + if (loading) return <Spinner /> + + if (error) return <Error error={error} /> + + return ( + <ErrorBoundary> + <FormTitle title="Idealbiotop" /> + <FormContainer> + <DateField + name="erstelldatum" + label="Erstelldatum" + value={row.erstelldatum} + saveToDb={saveToDb} + error={fieldErrors.erstelldatum} + /> + <Section>Lage</Section> + <TextField + name="hoehenlage" + label="Höhe" + type="text" + multiLine + value={row.hoehenlage} + saveToDb={saveToDb} + error={fieldErrors.hoehenlage} + /> + <TextField + name="region" + label="Region" + type="text" + multiLine + value={row.region} + saveToDb={saveToDb} + error={fieldErrors.region} + /> + <TextField + name="exposition" + label="Exposition" + type="text" + multiLine + value={row.exposition} + saveToDb={saveToDb} + error={fieldErrors.exposition} + /> + <TextField + name="besonnung" + label="Besonnung" + type="text" + multiLine + value={row.besonnung} + saveToDb={saveToDb} + error={fieldErrors.besonnung} + /> + <TextField + name="hangneigung" + label="Hangneigung" + type="text" + multiLine + value={row.hangneigung} + saveToDb={saveToDb} + error={fieldErrors.hangneigung} + /> + <Section>Boden</Section> + <TextField + name="bodenTyp" + label="Typ" + type="text" + multiLine + value={row.bodenTyp} + saveToDb={saveToDb} + error={fieldErrors.bodenTyp} + /> + <TextField + name="bodenKalkgehalt" + label="Kalkgehalt" + type="text" + multiLine + value={row.bodenKalkgehalt} + saveToDb={saveToDb} + error={fieldErrors.bodenKalkgehalt} + /> + <TextField + name="bodenDurchlaessigkeit" + label="Durchlässigkeit" + type="text" + multiLine + value={row.bodenDurchlaessigkeit} + saveToDb={saveToDb} + error={fieldErrors.bodenDurchlaessigkeit} + /> + <TextField + name="bodenHumus" + label="Humus" + type="text" + multiLine + value={row.bodenHumus} + saveToDb={saveToDb} + error={fieldErrors.bodenHumus} + /> + <TextField + name="bodenNaehrstoffgehalt" + label="Nährstoffgehalt" + type="text" + multiLine + value={row.bodenNaehrstoffgehalt} + saveToDb={saveToDb} + error={fieldErrors.bodenNaehrstoffgehalt} + /> + <TextField + name="wasserhaushalt" + label="Wasserhaushalt" + type="text" + multiLine + value={row.wasserhaushalt} + saveToDb={saveToDb} + error={fieldErrors.wasserhaushalt} + /> + <Section>Vegetation</Section> + <TextField + name="konkurrenz" + label="Konkurrenz" + type="text" + multiLine + value={row.konkurrenz} + saveToDb={saveToDb} + error={fieldErrors.konkurrenz} + /> + <TextField + name="moosschicht" + label="Moosschicht" + type="text" + multiLine + value={row.moosschicht} + saveToDb={saveToDb} + error={fieldErrors.moosschicht} + /> + <TextField + name="krautschicht" + label="Krautschicht" + type="text" + multiLine + value={row.krautschicht} + saveToDb={saveToDb} + error={fieldErrors.krautschicht} + /> + <TextField + name="strauchschicht" + label="Strauchschicht" + type="text" + multiLine + value={row.strauchschicht} + saveToDb={saveToDb} + error={fieldErrors.strauchschicht} + /> + <TextField + name="baumschicht" + label="Baumschicht" + type="text" + multiLine + value={row.baumschicht} + saveToDb={saveToDb} + error={fieldErrors.baumschicht} + /> + <TextField + name="bemerkungen" + label="Bemerkungen" + type="text" + multiLine + value={row.bemerkungen} + saveToDb={saveToDb} + error={fieldErrors.bemerkungen} + /> + </FormContainer> + </ErrorBoundary> + ) + }), +) diff --git a/src/components/Projekte/Daten/Idealbiotop/List.jsx b/src/components/Projekte/Daten/Idealbiotop/List.jsx new file mode 100644 index 0000000000..261cd8858b --- /dev/null +++ b/src/components/Projekte/Daten/Idealbiotop/List.jsx @@ -0,0 +1,16 @@ +import { memo } from 'react' + +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' +import { useIdealbiotopNavData } from '../../../../modules/useIdealbiotopNavData.js' + +export const List = memo(() => { + const { navData, isLoading, error } = useIdealbiotopNavData() + + if (isLoading) return <Spinner /> + + if (error) return <Error error={error} /> + + return <SharedList navData={navData} /> +}) diff --git a/src/components/Projekte/Daten/Idealbiotop/index.jsx b/src/components/Projekte/Daten/Idealbiotop/index.jsx index 348e25e095..1c0e107474 100644 --- a/src/components/Projekte/Daten/Idealbiotop/index.jsx +++ b/src/components/Projekte/Daten/Idealbiotop/index.jsx @@ -1,294 +1,14 @@ -import { memo, useState, useCallback, useContext, useMemo } from 'react' -import styled from '@emotion/styled' -import { observer } from 'mobx-react-lite' -import { useApolloClient, useQuery, gql } from '@apollo/client' -import { useParams } from 'react-router' +import { memo } from 'react' +import { useAtom } from 'jotai' -import { TextField } from '../../../shared/TextField.jsx' -import { DateField } from '../../../shared/Date.jsx' -import { constants } from '../../../../modules/constants.js' -import { query } from './query.js' -import { StoreContext } from '../../../../storeContext.js' -import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' -import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { Error } from '../../../shared/Error.jsx' -import { idealbiotop } from '../../../shared/fragments.js' -import { Spinner } from '../../../shared/Spinner.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Idealbiotop } from './Idealbiotop.jsx' +import { List } from './List.jsx' -const FormContainer = styled.div` - padding: 0 10px; - height: 100%; - column-width: ${constants.columnWidth}px; -` -const Section = styled.div` - padding-top: 20px; - padding-bottom: 7px; - font-weight: bold; - break-after: avoid; - &:after { - content: ':'; - } -` -const simplebarStyle = { maxHeight: '100%', height: '100%' } +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) -const fieldTypes = { - apId: 'UUID', - erstelldatum: 'Date', - hoehenlage: 'String', - region: 'String', - exposition: 'String', - besonnung: 'String', - hangneigung: 'String', - bodenTyp: 'String', - bodenKalkgehalt: 'String', - bodenDurchlaessigkeit: 'String', - bodenHumus: 'String', - bodenNaehrstoffgehalt: 'String', - wasserhaushalt: 'String', - konkurrenz: 'String', - moosschicht: 'String', - krautschicht: 'String', - strauchschicht: 'String', - baumschicht: 'String', - bemerkungen: 'String', -} + if (isDesktopView) return <Idealbiotop /> -export const Component = memo( - observer(() => { - const { apId } = useParams() - - const store = useContext(StoreContext) - const client = useApolloClient() - - const [fieldErrors, setFieldErrors] = useState({}) - - const { data, loading, error } = useQuery(query, { - variables: { - id: apId, - }, - }) - - const row = useMemo( - () => data?.allIdealbiotops?.nodes?.[0] ?? {}, - [data?.allIdealbiotops?.nodes], - ) - - const saveToDb = useCallback( - async (event) => { - const field = event.target.name - const value = ifIsNumericAsNumber(event.target.value) - - const variables = { - id: apId, - [field]: value, - changedBy: store.user.name, - } - try { - await client.mutate({ - mutation: gql` - mutation updateIdealbiotop( - $id: UUID! - $${field}: ${fieldTypes[field]} - $changedBy: String - ) { - updateIdealbiotopById( - input: { - id: $id - idealbiotopPatch: { - ${field}: $${field} - changedBy: $changedBy - } - } - ) { - idealbiotop { - ...IdealbiotopFields - } - } - } - ${idealbiotop} - `, - variables, - }) - } catch (error) { - return setFieldErrors({ [field]: error.message }) - } - setFieldErrors({}) - }, - [client, row, store.user.name], - ) - - if (loading) return <Spinner /> - - if (error) return <Error error={error} /> - - return ( - <ErrorBoundary> - <FormContainer> - <DateField - name="erstelldatum" - label="Erstelldatum" - value={row.erstelldatum} - saveToDb={saveToDb} - error={fieldErrors.erstelldatum} - /> - <Section>Lage</Section> - <TextField - name="hoehenlage" - label="Höhe" - type="text" - multiLine - value={row.hoehenlage} - saveToDb={saveToDb} - error={fieldErrors.hoehenlage} - /> - <TextField - name="region" - label="Region" - type="text" - multiLine - value={row.region} - saveToDb={saveToDb} - error={fieldErrors.region} - /> - <TextField - name="exposition" - label="Exposition" - type="text" - multiLine - value={row.exposition} - saveToDb={saveToDb} - error={fieldErrors.exposition} - /> - <TextField - name="besonnung" - label="Besonnung" - type="text" - multiLine - value={row.besonnung} - saveToDb={saveToDb} - error={fieldErrors.besonnung} - /> - <TextField - name="hangneigung" - label="Hangneigung" - type="text" - multiLine - value={row.hangneigung} - saveToDb={saveToDb} - error={fieldErrors.hangneigung} - /> - <Section>Boden</Section> - <TextField - name="bodenTyp" - label="Typ" - type="text" - multiLine - value={row.bodenTyp} - saveToDb={saveToDb} - error={fieldErrors.bodenTyp} - /> - <TextField - name="bodenKalkgehalt" - label="Kalkgehalt" - type="text" - multiLine - value={row.bodenKalkgehalt} - saveToDb={saveToDb} - error={fieldErrors.bodenKalkgehalt} - /> - <TextField - name="bodenDurchlaessigkeit" - label="Durchlässigkeit" - type="text" - multiLine - value={row.bodenDurchlaessigkeit} - saveToDb={saveToDb} - error={fieldErrors.bodenDurchlaessigkeit} - /> - <TextField - name="bodenHumus" - label="Humus" - type="text" - multiLine - value={row.bodenHumus} - saveToDb={saveToDb} - error={fieldErrors.bodenHumus} - /> - <TextField - name="bodenNaehrstoffgehalt" - label="Nährstoffgehalt" - type="text" - multiLine - value={row.bodenNaehrstoffgehalt} - saveToDb={saveToDb} - error={fieldErrors.bodenNaehrstoffgehalt} - /> - <TextField - name="wasserhaushalt" - label="Wasserhaushalt" - type="text" - multiLine - value={row.wasserhaushalt} - saveToDb={saveToDb} - error={fieldErrors.wasserhaushalt} - /> - <Section>Vegetation</Section> - <TextField - name="konkurrenz" - label="Konkurrenz" - type="text" - multiLine - value={row.konkurrenz} - saveToDb={saveToDb} - error={fieldErrors.konkurrenz} - /> - <TextField - name="moosschicht" - label="Moosschicht" - type="text" - multiLine - value={row.moosschicht} - saveToDb={saveToDb} - error={fieldErrors.moosschicht} - /> - <TextField - name="krautschicht" - label="Krautschicht" - type="text" - multiLine - value={row.krautschicht} - saveToDb={saveToDb} - error={fieldErrors.krautschicht} - /> - <TextField - name="strauchschicht" - label="Strauchschicht" - type="text" - multiLine - value={row.strauchschicht} - saveToDb={saveToDb} - error={fieldErrors.strauchschicht} - /> - <TextField - name="baumschicht" - label="Baumschicht" - type="text" - multiLine - value={row.baumschicht} - saveToDb={saveToDb} - error={fieldErrors.baumschicht} - /> - <TextField - name="bemerkungen" - label="Bemerkungen" - type="text" - multiLine - value={row.bemerkungen} - saveToDb={saveToDb} - error={fieldErrors.bemerkungen} - /> - </FormContainer> - </ErrorBoundary> - ) - }), -) + return <List /> +}) diff --git a/src/components/Projekte/Daten/IdealbiotopRouter/index.jsx b/src/components/Projekte/Daten/IdealbiotopRouter/index.jsx deleted file mode 100644 index 9d9bb1647d..0000000000 --- a/src/components/Projekte/Daten/IdealbiotopRouter/index.jsx +++ /dev/null @@ -1,85 +0,0 @@ -import { useCallback, Suspense } from 'react' -import styled from '@emotion/styled' -import Tabs from '@mui/material/Tabs' -import Tab from '@mui/material/Tab' -import { useParams, Outlet, useNavigate, useLocation } from 'react-router' - -import { FormTitle } from '../../../shared/FormTitle/index.jsx' -import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' - -const Container = styled.div` - flex-grow: 1; - display: flex; - flex-direction: column; - overflow: hidden; -` -const StyledTab = styled(Tab)` - text-transform: none !important; -` -const TabContentContainer = styled.div` - overflow-y: auto; - scrollbar-width: thin; - flex-grow: 1; - display: flex; - flex-direction: column; -` -const TabContent = styled.div` - flex-grow: 1; - display: flex; - flex-direction: column; -` - -export const Component = () => { - const { apId } = useParams() - const navigate = useNavigate() - const { pathname, search } = useLocation() - - const onChangeTab = useCallback( - (event, value) => - pathname.endsWith(apId) ? - navigate(`./${value}${search}`) - : navigate(`${value}${search}`), - [pathname, apId, navigate, search], - ) - const path = pathname.split('/').filter((el) => !!el) - const lastPathEl = path.at(-1) - - return ( - <ErrorBoundary> - <Container> - <FormTitle title="Idealbiotop" /> - <Tabs - value={ - lastPathEl === 'Idealbiotop' ? 'Idealbiotop' - : pathname.includes(`${apId}/Idealbiotop/Dateien`) ? - 'Dateien' - : 'Idealbiotop' - } - onChange={onChangeTab} - indicatorColor="primary" - textColor="primary" - centered - > - <StyledTab - label="Idealbiotop" - value="Idealbiotop" - data-id="Idealbiotop" - /> - <StyledTab - label="Dateien" - value="Dateien" - data-id="Dateien" - /> - </Tabs> - <TabContentContainer> - <TabContent> - <Suspense fallback={<Spinner />}> - <Outlet /> - </Suspense> - </TabContent> - </TabContentContainer> - </Container> - </ErrorBoundary> - ) -} diff --git a/src/components/Projekte/Daten/Pop/Auswertung/index.jsx b/src/components/Projekte/Daten/Pop/Auswertung/index.jsx deleted file mode 100644 index 9cf59320f2..0000000000 --- a/src/components/Projekte/Daten/Pop/Auswertung/index.jsx +++ /dev/null @@ -1,206 +0,0 @@ -import { useCallback } from 'react' -import { useQuery } from '@apollo/client' -import sortBy from 'lodash/sortBy' -import { - AreaChart, - Area, - XAxis, - YAxis, - Tooltip, - CartesianGrid, - ResponsiveContainer, -} from 'recharts' -import CircularProgress from '@mui/material/CircularProgress' -import styled from '@emotion/styled' -import { IoMdInformationCircleOutline } from 'react-icons/io' -import IconButton from '@mui/material/IconButton' -import MuiTooltip from '@mui/material/Tooltip' -import { useParams } from 'react-router' - -import { query } from './query.js' -import { CustomTooltip } from './CustomTooltip.jsx' -import { exists } from '../../../../../modules/exists.js' -import { Error } from '../../../../shared/Error.jsx' - -const SpinnerContainer = styled.div` - height: 400px; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -` -const SpinnerText = styled.div` - padding: 10px; -` -const NoDataContainer = styled.div` - margin: 20px; - margin-bottom: 40px; - text-align: center; -` -const TitleRow = styled.div` - width: 100%; - display: flex; - justify-content: center; - align-items: center; - margin-top: 15px; -` -const Title = styled.h4` - margin-bottom: 0; - margin-top: 0; - padding: 0 10px; -` -const Container = styled(ResponsiveContainer)` - overflow: hidden; -` - -const colorUrspruenglich = 'rgba(46,125,50,0.3)' -const colorAngesiedelt = 'rgba(245,141,66,1)' -const formatNumber = (tickItem) => { - const value = - exists(tickItem) && tickItem?.toLocaleString ? - tickItem.toLocaleString('de-ch') - : null - return value -} - -export const Component = ({ height = 400 }) => { - const { apId, popId } = useParams() - - const { data, error, loading } = useQuery(query, { - variables: { apId, id: popId }, - }) - - const tpopsData = data?.allTpops?.nodes ?? [] - const tpopMengeRawData = data?.popAuswTpopMenge?.nodes ?? [] - const tpopMengeData = tpopMengeRawData.map((e) => ({ - jahr: e.jahr, - ...JSON.parse(e.values), - })) - const nonUniqueTpopIdsWithData = tpopMengeData.flatMap((d) => - Object.entries(d) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .filter(([key, value]) => key !== 'jahr') - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .filter(([key, value]) => exists(value)) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .map(([key, value]) => key), - ) - const tpopIdsWithData = [...new Set(nonUniqueTpopIdsWithData)] - const tpopIdsWithDataSorted = sortBy(tpopIdsWithData, (id) => { - const tpop = tpopsData.find((d) => d.id === id) - if (tpop) return tpop.nr - return id - }) - - const zielEinheit = - data?.allEkzaehleinheits?.nodes?.[0] - ?.tpopkontrzaehlEinheitWerteByZaehleinheitId?.text ?? '(keine Zielenheit)' - - const onClickMoreInfo = useCallback(() => { - const url = 'https://apflora.ch/Dokumentation/art-auswertung-pop-menge' - if (window.matchMedia('(display-mode: standalone)').matches) { - return window.open(url, '_blank', 'toolbar=no') - } - window.open(url) - }, []) - - if (error) return <Error error={error} /> - - // need to disable animation on lines or labels will not show on first render - // https://github.com/recharts/recharts/issues/1821 - - //console.log('AP, PopMenge, popMengeData:', popMengeData) - - return ( - <> - {loading ? - <SpinnerContainer> - <CircularProgress /> - <SpinnerText>lade Mengen nach Teil-Populationen...</SpinnerText> - </SpinnerContainer> - : tpopMengeData.length ? - <> - <TitleRow> - <Title>{`"${zielEinheit}" nach Teil-Populationen`} - - - - - - - - - - - {tpopIdsWithDataSorted.reverse().map((id) => { - const tpop = tpopsData.find((p) => p.id === id) - let color - if (!tpop) { - color = 'grey' - } else { - const isUrspruenglich = tpop?.status < 200 - color = - isUrspruenglich ? colorUrspruenglich : colorAngesiedelt - } - - return ( - - ) - })} - } /> - - - - - : <> - - {`"${zielEinheit}" nach Teil-Populationen`} - - - - - - - Keine Daten gefunden - - } - - ) -} diff --git a/src/components/Projekte/Daten/Pop/Dateien.jsx b/src/components/Projekte/Daten/Pop/Dateien.jsx index 8d3507e008..80b911e8ec 100644 --- a/src/components/Projekte/Daten/Pop/Dateien.jsx +++ b/src/components/Projekte/Daten/Pop/Dateien.jsx @@ -1,15 +1,36 @@ import { memo } from 'react' import { useParams } from 'react-router' +import { useApolloClient, useQuery, gql } from '@apollo/client' import { FilesRouter } from '../../../shared/Files/index.jsx' +import { FormTitle } from '../../../shared/FormTitle/index.jsx' export const Component = memo(() => { const { popId } = useParams() + const client = useApolloClient() + const { data } = useQuery( + gql` + query popByIdForPopDateienQuery($id: UUID!) { + popById(id: $id) { + id + label + } + } + `, + { + variables: { id: popId }, + }, + ) + const label = data?.popById?.label ?? 'Population' + return ( - + <> + + + ) }) diff --git a/src/components/Projekte/Daten/Pop/Historien.jsx b/src/components/Projekte/Daten/Pop/Historien.jsx index f2ab5e2cb0..8b97a4ea00 100644 --- a/src/components/Projekte/Daten/Pop/Historien.jsx +++ b/src/components/Projekte/Daten/Pop/Historien.jsx @@ -6,6 +6,7 @@ import { useParams } from 'react-router' import { Spinner } from '../../../shared/Spinner.jsx' import { History as HistoryComponent } from '../../../shared/History/index.jsx' import { appBaseUrl } from '../../../../modules/appBaseUrl.js' +import { FormTitle } from '../../../shared/FormTitle/index.jsx' const query = gql` query popHistoryQuery($popId: UUID!) { @@ -21,6 +22,7 @@ const query = gql` } nr name + label status popStatusWerteByStatus { id @@ -71,11 +73,13 @@ const query = gql` ` const Container = styled.div` - padding: 8px 25px 0 25px; + padding: 10px; display: flex; flex-direction: column; flex-grow: 1; overflow: hidden; + overflow-y: auto; + scrollbar-width: thin; ` const ErrorContainer = styled.div` padding: 25px; @@ -86,6 +90,9 @@ const DocLink = styled.span` ` const DocLine = styled.p` margin-bottom: 0; + &:first-of-type { + margin-top: 0; + } ` const Aenderung = styled.span` background-color: rgba(216, 67, 21, 0.2); @@ -106,6 +113,7 @@ export const Component = () => { const row = data?.popById const rows = data?.allPopHistories.nodes ?? [] + const label = row?.label ?? 'Population' const openDocs = useCallback(() => { const url = `${appBaseUrl()}/Dokumentation/historisierung` @@ -124,73 +132,77 @@ export const Component = () => { } return ( - - - Jährlich historisierte Daten der Population ( - Dokumentation - ). - - - Änderungen zum{' '} - aktuellen Zustand sind hervorgehoben. - - {rows.map((r) => { - const dataArray = [ - { - valueInRow: row?.apByApId?.aeTaxonomyByArtId?.artname ?? row?.apId, - valueInHist: r?.apByApId?.aeTaxonomyByArtId?.artname ?? r?.apId, - label: 'Art', - }, - { - valueInRow: row?.nr, - valueInHist: r?.nr, - label: 'Nr.', - }, - { - valueInRow: row?.name, - valueInHist: r?.name, - label: 'Name', - }, - { - valueInRow: row?.bekanntSeit, - valueInHist: r?.bekanntSeit, - label: 'bekannt seit', - }, - { - valueInRow: row?.popStatusWerteByStatus?.text ?? row?.status, - valueInHist: r?.popStatusWerteByStatus?.text ?? r?.status, - label: 'Status', - }, - { - valueInRow: row?.statusUnklar, - valueInHist: r?.statusUnklar, - label: 'Status unklar', - }, - { - valueInRow: row?.statusUnklarBegruendung, - valueInHist: r?.statusUnklarBegruendung, - label: 'Begründung (für Status unklar)', - }, - { - valueInRow: row?.geomPoint?.x, - valueInHist: r?.geomPoint?.x, - label: 'Längengrad', - }, - { - valueInRow: row?.geomPoint?.y, - valueInHist: r?.geomPoint?.y, - label: 'Breitengrad', - }, - ] + <> + + + + Jährlich historisierte Daten der Population ( + Dokumentation + ). + + + Änderungen zum{' '} + aktuellen Zustand sind hervorgehoben. + + {rows.map((r) => { + const dataArray = [ + { + valueInRow: + row?.apByApId?.aeTaxonomyByArtId?.artname ?? row?.apId, + valueInHist: r?.apByApId?.aeTaxonomyByArtId?.artname ?? r?.apId, + label: 'Art', + }, + { + valueInRow: row?.nr, + valueInHist: r?.nr, + label: 'Nr.', + }, + { + valueInRow: row?.name, + valueInHist: r?.name, + label: 'Name', + }, + { + valueInRow: row?.bekanntSeit, + valueInHist: r?.bekanntSeit, + label: 'bekannt seit', + }, + { + valueInRow: row?.popStatusWerteByStatus?.text ?? row?.status, + valueInHist: r?.popStatusWerteByStatus?.text ?? r?.status, + label: 'Status', + }, + { + valueInRow: row?.statusUnklar, + valueInHist: r?.statusUnklar, + label: 'Status unklar', + }, + { + valueInRow: row?.statusUnklarBegruendung, + valueInHist: r?.statusUnklarBegruendung, + label: 'Begründung (für Status unklar)', + }, + { + valueInRow: row?.geomPoint?.x, + valueInHist: r?.geomPoint?.x, + label: 'Längengrad', + }, + { + valueInRow: row?.geomPoint?.y, + valueInHist: r?.geomPoint?.y, + label: 'Breitengrad', + }, + ] - return ( - - ) - })} - + return ( + + ) + })} + + ) } diff --git a/src/components/Projekte/Daten/Pop/List.jsx b/src/components/Projekte/Daten/Pop/List.jsx new file mode 100644 index 0000000000..5caf7484b4 --- /dev/null +++ b/src/components/Projekte/Daten/Pop/List.jsx @@ -0,0 +1,23 @@ +import { memo } from 'react' + +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from './Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' +import { usePopNavData } from '../../../../modules/usePopNavData.js' + +export const List = memo(() => { + const { navData, isLoading, error } = usePopNavData() + + if (isLoading) return + + if (error) return + + return ( + + ) +}) diff --git a/src/components/Projekte/Daten/PopRouter/Menu.jsx b/src/components/Projekte/Daten/Pop/Menu.jsx similarity index 91% rename from src/components/Projekte/Daten/PopRouter/Menu.jsx rename to src/components/Projekte/Daten/Pop/Menu.jsx index f93c0723e5..71db1ddb76 100644 --- a/src/components/Projekte/Daten/PopRouter/Menu.jsx +++ b/src/components/Projekte/Daten/Pop/Menu.jsx @@ -14,15 +14,17 @@ import MenuItem from '@mui/material/MenuItem' import Tooltip from '@mui/material/Tooltip' import isEqual from 'lodash/isEqual' import styled from '@emotion/styled' +import { useAtom } from 'jotai' import { MenuBar } from '../../../shared/MenuBar/index.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' import { openLowerNodes } from '../../TreeContainer/openLowerNodes/index.js' import { closeLowerNodes } from '../../TreeContainer/closeLowerNodes.js' import { moveTo } from '../../../../modules/moveTo/index.js' import { copyTo } from '../../../../modules/copyTo/index.js' +import { hideTreeAtom } from '../../../../JotaiStore/index.js' const MoveIcon = styled(MdOutlineMoveDown)` color: ${(props) => @@ -41,7 +43,7 @@ export const Menu = memo( const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { projId, apId, popId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { setMoving, moving, setCopying, copying } = store const onClickAdd = useCallback(async () => { @@ -74,9 +76,12 @@ export const Menu = memo( tanstackQueryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + tanstackQueryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) const id = result?.data?.createPop?.pop?.id navigate( - `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${id}${search}`, + `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${id}/Population${search}`, ) }, [apId, client, store, tanstackQueryClient, navigate, search, projId]) @@ -124,6 +129,9 @@ export const Menu = memo( tanstackQueryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + tanstackQueryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) // navigate to parent navigate(`/Daten/Projekte/${projId}/Arten/${apId}/Populationen${search}`) }, [ @@ -258,10 +266,12 @@ export const Menu = memo( }) }, [setCopying]) + const [hideTree] = useAtom(hideTreeAtom) + return ( @@ -276,16 +286,20 @@ export const Menu = memo( - - - - - - - - - - + {!hideTree && ( + + + + + + )} + {!hideTree && ( + + + + + + )} { + const { projId, apId, popId } = useParams() + + const store = useContext(MobxContext) + const queryClient = useQueryClient() + const client = useApolloClient() + + const [fieldErrors, setFieldErrors] = useState({}) + + const { data, error, refetch } = useQuery(query, { + variables: { id: popId }, + }) + + const row = useMemo(() => data?.popById ?? {}, [data?.popById]) + + const saveToDb = useCallback( + async (event) => { + const field = event.target.name + const value = ifIsNumericAsNumber(event.target.value) + + const variables = { + id: row.id, + [field]: value, + changedBy: store.user.name, + } + try { + await client.mutate({ + mutation: gql` + mutation updatePopForPop( + $id: UUID! + $${field}: ${fieldTypes[field]} + $changedBy: String + ) { + updatePopById( + input: { + id: $id + popPatch: { + ${field}: $${field} + changedBy: $changedBy + } + } + ) { + pop { + ...PopFields + } + } + } + ${pop} + `, + variables, + }) + } catch (error) { + return setFieldErrors({ [field]: error.message }) + } + // update pop on map + if ( + (value && + row && + ((field === 'lv95Y' && row.lv95X) || + (field === 'lv95X' && row.lv95Y))) || + (!value && (field === 'lv95Y' || field === 'lv95X')) + ) { + client.refetchQueries({ + include: ['PopForMapQuery'], + }) + } + setFieldErrors({}) + if (['name', 'nr'].includes(field)) { + queryClient.invalidateQueries({ + queryKey: [`treePop`], + }) + } + }, + [client, queryClient, row, store.user.name], + ) + + if (error) return + + return ( + + }> + + + + + + + + + + + + ) + }), +) diff --git a/src/components/Projekte/Daten/Pop/index.jsx b/src/components/Projekte/Daten/Pop/index.jsx index b1ac2a40bd..d2cface430 100644 --- a/src/components/Projekte/Daten/Pop/index.jsx +++ b/src/components/Projekte/Daten/Pop/index.jsx @@ -1,164 +1,14 @@ -import { memo, useContext, useCallback, useState, useMemo } from 'react' -import styled from '@emotion/styled' -import { observer } from 'mobx-react-lite' -import { useApolloClient, useQuery, gql } from '@apollo/client' -import { useOutletContext } from 'react-router' -import { useQueryClient } from '@tanstack/react-query' +import { memo } from 'react' +import { useAtom } from 'jotai' -import { TextField } from '../../../shared/TextField.jsx' -import { TextFieldWithInfo } from '../../../shared/TextFieldWithInfo.jsx' -import { Status } from '../../../shared/Status.jsx' -import { Checkbox2States } from '../../../shared/Checkbox2States.jsx' -import { StoreContext } from '../../../../storeContext.js' -import { Coordinates } from '../../../shared/Coordinates.jsx' -import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' -import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { pop } from '../../../shared/fragments.js' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Pop } from './Pop.jsx' +import { List } from './List.jsx' -const FormContainer = styled.div` - display: flex; - flex-direction: column; - flex-grow: 1; - overflow: hidden; - overflow-y: auto; - scrollbar-width: thin; - padding: 10px; - height: 100%; -` +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) -const fieldTypes = { - apId: 'UUID', - nr: 'Int', - name: 'String', - status: 'Int', - statusUnklar: 'Boolean', - statusUnklarBegruendung: 'String', - bekanntSeit: 'Int', -} + if (isDesktopView) return -export const Component = memo( - observer(() => { - const { data, refetchPop } = useOutletContext() - - const store = useContext(StoreContext) - const queryClient = useQueryClient() - const client = useApolloClient() - - const [fieldErrors, setFieldErrors] = useState({}) - - const row = useMemo(() => data?.popById ?? {}, [data?.popById]) - - const saveToDb = useCallback( - async (event) => { - const field = event.target.name - const value = ifIsNumericAsNumber(event.target.value) - - const variables = { - id: row.id, - [field]: value, - changedBy: store.user.name, - } - try { - await client.mutate({ - mutation: gql` - mutation updatePopForPop( - $id: UUID! - $${field}: ${fieldTypes[field]} - $changedBy: String - ) { - updatePopById( - input: { - id: $id - popPatch: { - ${field}: $${field} - changedBy: $changedBy - } - } - ) { - pop { - ...PopFields - } - } - } - ${pop} - `, - variables, - }) - } catch (error) { - return setFieldErrors({ [field]: error.message }) - } - // update pop on map - if ( - (value && - row && - ((field === 'lv95Y' && row.lv95X) || - (field === 'lv95X' && row.lv95Y))) || - (!value && (field === 'lv95Y' || field === 'lv95X')) - ) { - client.refetchQueries({ - include: ['PopForMapQuery'], - }) - } - setFieldErrors({}) - if (['name', 'nr'].includes(field)) { - queryClient.invalidateQueries({ - queryKey: [`treePop`], - }) - } - }, - [client, queryClient, row, store.user.name], - ) - - return ( - - - - - - - - - - - ) - }), -) + return +}) diff --git a/src/components/Projekte/Daten/PopRouter/query.js b/src/components/Projekte/Daten/Pop/query.js similarity index 83% rename from src/components/Projekte/Daten/PopRouter/query.js rename to src/components/Projekte/Daten/Pop/query.js index 8cb2aa11f8..52c56db12b 100644 --- a/src/components/Projekte/Daten/PopRouter/query.js +++ b/src/components/Projekte/Daten/Pop/query.js @@ -3,7 +3,7 @@ import { gql } from '@apollo/client' import { pop } from '../../../shared/fragments.js' export const query = gql` - query popByIdQuery($id: UUID!) { + query popByIdForPopFormQuery($id: UUID!) { popById(id: $id) { ...PopFields apByApId { diff --git a/src/components/Projekte/Daten/Pop/Auswertung/CustomTooltip.jsx b/src/components/Projekte/Daten/PopAuswertung/CustomTooltip.jsx similarity index 96% rename from src/components/Projekte/Daten/Pop/Auswertung/CustomTooltip.jsx rename to src/components/Projekte/Daten/PopAuswertung/CustomTooltip.jsx index 647f1f47fb..abbb318a8f 100644 --- a/src/components/Projekte/Daten/Pop/Auswertung/CustomTooltip.jsx +++ b/src/components/Projekte/Daten/PopAuswertung/CustomTooltip.jsx @@ -2,7 +2,7 @@ import { memo } from 'react' import styled from '@emotion/styled' import sortBy from 'lodash/sortBy' -import { exists } from '../../../../../modules/exists.js' +import { exists } from '../../../../modules/exists.js' const Popup = styled.div` background-color: white; diff --git a/src/components/Projekte/Daten/PopAuswertung/index.jsx b/src/components/Projekte/Daten/PopAuswertung/index.jsx new file mode 100644 index 0000000000..dd5e50a0cf --- /dev/null +++ b/src/components/Projekte/Daten/PopAuswertung/index.jsx @@ -0,0 +1,211 @@ +import { useCallback } from 'react' +import { useQuery, gql } from '@apollo/client' +import sortBy from 'lodash/sortBy' +import { + AreaChart, + Area, + XAxis, + YAxis, + Tooltip, + CartesianGrid, + ResponsiveContainer, +} from 'recharts' +import CircularProgress from '@mui/material/CircularProgress' +import styled from '@emotion/styled' +import { IoMdInformationCircleOutline } from 'react-icons/io' +import IconButton from '@mui/material/IconButton' +import MuiTooltip from '@mui/material/Tooltip' +import { useParams } from 'react-router' + +import { query } from './query.js' +import { CustomTooltip } from './CustomTooltip.jsx' +import { exists } from '../../../../modules/exists.js' +import { Error } from '../../../shared/Error.jsx' +import { FormTitle } from '../../../shared/FormTitle/index.jsx' + +const SpinnerContainer = styled.div` + height: 400px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +` +const SpinnerText = styled.div` + padding: 10px; +` +const NoDataContainer = styled.div` + margin: 20px; + margin-bottom: 40px; + text-align: center; +` +const TitleRow = styled.div` + width: 100%; + display: flex; + justify-content: center; + align-items: center; + margin-top: 15px; +` +const Title = styled.h4` + margin-bottom: 0; + margin-top: 0; + padding: 0 10px; +` +const Container = styled(ResponsiveContainer)` + overflow: hidden; +` + +const colorUrspruenglich = 'rgba(46,125,50,0.3)' +const colorAngesiedelt = 'rgba(245,141,66,1)' +const formatNumber = (tickItem) => { + const value = + exists(tickItem) && tickItem?.toLocaleString ? + tickItem.toLocaleString('de-ch') + : null + return value +} + +export const Component = ({ height = 400 }) => { + const { apId, popId } = useParams() + + const { data, error, loading } = useQuery(query, { + variables: { apId, id: popId }, + }) + + const popLabel = data?.popById?.label ?? 'Population' + const tpopsData = data?.allTpops?.nodes ?? [] + const tpopMengeRawData = data?.popAuswTpopMenge?.nodes ?? [] + const tpopMengeData = tpopMengeRawData.map((e) => ({ + jahr: e.jahr, + ...JSON.parse(e.values), + })) + const nonUniqueTpopIdsWithData = tpopMengeData.flatMap((d) => + Object.entries(d) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .filter(([key, value]) => key !== 'jahr') + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .filter(([key, value]) => exists(value)) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .map(([key, value]) => key), + ) + const tpopIdsWithData = [...new Set(nonUniqueTpopIdsWithData)] + const tpopIdsWithDataSorted = sortBy(tpopIdsWithData, (id) => { + const tpop = tpopsData.find((d) => d.id === id) + if (tpop) return tpop.nr + return id + }) + + const zielEinheit = + data?.allEkzaehleinheits?.nodes?.[0] + ?.tpopkontrzaehlEinheitWerteByZaehleinheitId?.text ?? '(keine Zielenheit)' + + const onClickMoreInfo = useCallback(() => { + const url = 'https://apflora.ch/Dokumentation/art-auswertung-pop-menge' + if (window.matchMedia('(display-mode: standalone)').matches) { + return window.open(url, '_blank', 'toolbar=no') + } + window.open(url) + }, []) + + if (error) return + + // need to disable animation on lines or labels will not show on first render + // https://github.com/recharts/recharts/issues/1821 + + //console.log('AP, PopMenge, popMengeData:', popMengeData) + + return ( + <> + + <> + {loading ? + + + lade Mengen nach Teil-Populationen... + + : tpopMengeData.length ? + <> + + {`"${zielEinheit}" nach Teil-Populationen`} + + + + + + + + + + + {tpopIdsWithDataSorted.reverse().map((id) => { + const tpop = tpopsData.find((p) => p.id === id) + let color + if (!tpop) { + color = 'grey' + } else { + const isUrspruenglich = tpop?.status < 200 + color = + isUrspruenglich ? colorUrspruenglich : colorAngesiedelt + } + + return ( + + ) + })} + } /> + + + + + : <> + + {`"${zielEinheit}" nach Teil-Populationen`} + + + + + + + Keine Daten gefunden + + } + + + ) +} diff --git a/src/components/Projekte/Daten/Pop/Auswertung/query.js b/src/components/Projekte/Daten/PopAuswertung/query.js similarity index 93% rename from src/components/Projekte/Daten/Pop/Auswertung/query.js rename to src/components/Projekte/Daten/PopAuswertung/query.js index 9f7fb114d4..1b8e915546 100644 --- a/src/components/Projekte/Daten/Pop/Auswertung/query.js +++ b/src/components/Projekte/Daten/PopAuswertung/query.js @@ -2,6 +2,10 @@ import { gql } from '@apollo/client' export const query = gql` query popAuswertungTpopMenge($apId: UUID!, $id: UUID!) { + popById(id: $id) { + id + label + } # function apflora.pop_ausw_tpop_menge popAuswTpopMenge(popid: $id) { nodes { diff --git a/src/components/Projekte/Daten/PopFilter/PopOrTabs.jsx b/src/components/Projekte/Daten/PopFilter/PopOrTabs.jsx index 1f24a27af4..4f08f9e540 100644 --- a/src/components/Projekte/Daten/PopFilter/PopOrTabs.jsx +++ b/src/components/Projekte/Daten/PopFilter/PopOrTabs.jsx @@ -4,7 +4,7 @@ import Tab from '@mui/material/Tab' import styled from '@emotion/styled' import { initial as pop } from '../../../../store/Tree/DataFilter/pop' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' const Row = styled.div`` const Title = styled.div` @@ -24,7 +24,7 @@ const StyledTab = styled(Tab)` ` export const PopOrTabs = ({ activeTab, setActiveTab, dataFilter }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { dataFilterAddOr } = store.tree const lastFilterIsEmpty = diff --git a/src/components/Projekte/Daten/PopFilter/index.jsx b/src/components/Projekte/Daten/PopFilter/index.jsx index d70c9152df..d43872ee81 100644 --- a/src/components/Projekte/Daten/PopFilter/index.jsx +++ b/src/components/Projekte/Daten/PopFilter/index.jsx @@ -10,7 +10,7 @@ import { Status } from '../../../shared/Status.jsx' import { Checkbox2States } from '../../../shared/Checkbox2States.jsx' import { FilterTitle } from '../../../shared/FilterTitle.jsx' import { query } from './query.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { Error } from '../../../shared/Error.jsx' @@ -50,7 +50,7 @@ export const PopFilter = memo( observer(() => { const { apId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { dataFilter: dataFilterRaw, nodeLabelFilter, diff --git a/src/components/Projekte/Daten/PopRouter/index.jsx b/src/components/Projekte/Daten/PopRouter/index.jsx deleted file mode 100644 index 85c821592f..0000000000 --- a/src/components/Projekte/Daten/PopRouter/index.jsx +++ /dev/null @@ -1,122 +0,0 @@ -import { useCallback, Suspense, useMemo } from 'react' -import Tabs from '@mui/material/Tabs' -import Tab from '@mui/material/Tab' -import styled from '@emotion/styled' -import { useParams, Outlet, useNavigate, useLocation } from 'react-router' -import { useApolloClient, useQuery, gql } from '@apollo/client' - -import { FormTitle } from '../../../shared/FormTitle/index.jsx' -import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { Error } from '../../../shared/Error.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { query } from '../PopRouter/query.js' -import { Menu } from './Menu.jsx' - -const Container = styled.div` - flex-grow: 1; - display: flex; - flex-direction: column; - overflow: hidden; -` -const StyledTab = styled(Tab)` - text-transform: none !important; -` -const TabContentContainer = styled.div` - overflow-y: auto; - scrollbar-width: thin; - flex-grow: 1; - display: flex; - flex-direction: column; -` -const TabContent = styled.div` - flex-grow: 1; - display: flex; - flex-direction: column; -` - -export const Component = () => { - const { popId } = useParams() - const navigate = useNavigate() - const { pathname, search } = useLocation() - - const { - data, - loading, - error, - refetch: refetchPop, - } = useQuery(query, { - variables: { - id: popId, - }, - }) - - const row = useMemo(() => data?.popById ?? {}, [data?.popById]) - - const onChangeTab = useCallback( - (event, value) => - pathname.endsWith(popId) ? - navigate(`./${value}${search}`) - : navigate(`${value}${search}`), - [pathname, popId, navigate, search], - ) - - return ( - - - } - /> - - - - - - - - - {loading ? - - : error ? - - : }> - - - } - - - - - ) -} diff --git a/src/components/Projekte/Daten/Popber/Menu.jsx b/src/components/Projekte/Daten/Popber/Menu.jsx index cc52580f70..5aa4136502 100644 --- a/src/components/Projekte/Daten/Popber/Menu.jsx +++ b/src/components/Projekte/Daten/Popber/Menu.jsx @@ -14,7 +14,7 @@ import styled from '@emotion/styled' import { MenuBar } from '../../../shared/MenuBar/index.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' const iconStyle = { color: 'white' } @@ -26,7 +26,7 @@ export const Menu = memo( const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { projId, apId, popId, popberId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result @@ -58,6 +58,9 @@ export const Menu = memo( tanstackQueryClient.invalidateQueries({ queryKey: [`treePopFolders`], }) + tanstackQueryClient.invalidateQueries({ + queryKey: [`treePop`], + }) const id = result?.data?.createPopber?.popber?.id navigate( `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Kontroll-Berichte/${id}${search}`, @@ -114,6 +117,9 @@ export const Menu = memo( tanstackQueryClient.invalidateQueries({ queryKey: [`treePopFolders`], }) + tanstackQueryClient.invalidateQueries({ + queryKey: [`treePop`], + }) // navigate to parent navigate( `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Kontroll-Berichte${search}`, diff --git a/src/components/Projekte/Daten/Popber/index.jsx b/src/components/Projekte/Daten/Popber/index.jsx index 9d8225f04b..bc9c6dd352 100644 --- a/src/components/Projekte/Daten/Popber/index.jsx +++ b/src/components/Projekte/Daten/Popber/index.jsx @@ -9,7 +9,7 @@ import { RadioButtonGroup } from '../../../shared/RadioButtonGroup.jsx' import { TextField } from '../../../shared/TextField.jsx' import { FormTitle } from '../../../shared/FormTitle/index.jsx' import { query } from './query.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { Spinner } from '../../../shared/Spinner.jsx' @@ -46,7 +46,7 @@ export const Component = memo( const client = useApolloClient() const queryClient = useQueryClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const [fieldErrors, setFieldErrors] = useState({}) @@ -127,7 +127,7 @@ export const Component = memo( } + MenuBarComponent={Menu} /> { + const store = useContext(MobxContext) + const { nodeLabelFilter } = store.tree + + const { navData, isLoading, error } = usePopbersNavData() + + if (isLoading) return + + if (error) return + + return ( + + ) + }), +) diff --git a/src/components/Projekte/Daten/Popbers/Menu.jsx b/src/components/Projekte/Daten/Popbers/Menu.jsx index f95fc2d101..2ffe76da9a 100644 --- a/src/components/Projekte/Daten/Popbers/Menu.jsx +++ b/src/components/Projekte/Daten/Popbers/Menu.jsx @@ -6,24 +6,22 @@ import { FaPlus } from 'react-icons/fa6' import IconButton from '@mui/material/IconButton' import Tooltip from '@mui/material/Tooltip' import { observer } from 'mobx-react-lite' -import { useAtom } from 'jotai' import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' +import { FilterButton } from '../../../shared/MenuBar/FilterButton.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' -import { LabelFilter, labelFilterWidth } from '../../../shared/LabelFilter.jsx' -import { listLabelFilterIsIconAtom } from '../../../../JotaiStore/index.js' +import { MobxContext } from '../../../../mobxContext.js' const iconStyle = { color: 'white' } export const Menu = memo( - observer(() => { + observer(({ toggleFilterInput }) => { const { search } = useLocation() const navigate = useNavigate() const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { popId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result @@ -55,18 +53,19 @@ export const Menu = memo( tanstackQueryClient.invalidateQueries({ queryKey: [`treePopFolders`], }) + tanstackQueryClient.invalidateQueries({ + queryKey: [`treePop`], + }) const id = result?.data?.createPopber?.popber?.id navigate(`./${id}${search}`) }, [client, store, tanstackQueryClient, navigate, search, popId]) - const [labelFilterIsIcon] = useAtom(listLabelFilterIsIconAtom) - return ( - + {!!toggleFilterInput && ( + + )} diff --git a/src/components/Projekte/Daten/Popbers/index.jsx b/src/components/Projekte/Daten/Popbers/index.jsx index 0eb783185e..e48409378d 100644 --- a/src/components/Projekte/Daten/Popbers/index.jsx +++ b/src/components/Projekte/Daten/Popbers/index.jsx @@ -1,47 +1,14 @@ -import { memo, useContext, useMemo } from 'react' -import { useApolloClient, gql } from '@apollo/client' -import { useQuery } from '@tanstack/react-query' -import { useParams } from 'react-router' -import { observer } from 'mobx-react-lite' +import { memo } from 'react' import { useAtom } from 'jotai' -import { StoreContext } from '../../../../storeContext.js' -import { createApsQuery } from '../../../../modules/createApsQuery.js' -import { createPopbersQuery } from '../../../../modules/createPopbersQuery.js' -import { List } from '../../../shared/List/index.jsx' -import { Menu } from './Menu.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Error } from '../../../shared/Error.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Pop } from '../Pop/index.jsx' +import { List } from './List.jsx' -export const Component = memo( - observer(() => { - const { popId } = useParams() - const apolloClient = useApolloClient() - const store = useContext(StoreContext) - const { popberGqlFilterForTree, nodeLabelFilter } = store.tree +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) - const { data, isLoading, error } = useQuery( - createPopbersQuery({ - popId, - popberGqlFilterForTree, - apolloClient, - }), - ) - const popbers = data?.data?.popById?.popbersByPopId?.nodes ?? [] - const totalCount = data?.data?.popById?.popbersCount?.totalCount ?? 0 + if (isDesktopView) return - if (isLoading) return - - if (error) return - - return ( - } - highlightSearchString={nodeLabelFilter.popber} - /> - ) - }), -) + return +}) diff --git a/src/components/Projekte/Daten/Popmassnber/Menu.jsx b/src/components/Projekte/Daten/Popmassnber/Menu.jsx index 9a8d668daf..7701449120 100644 --- a/src/components/Projekte/Daten/Popmassnber/Menu.jsx +++ b/src/components/Projekte/Daten/Popmassnber/Menu.jsx @@ -14,7 +14,7 @@ import styled from '@emotion/styled' import { MenuBar } from '../../../shared/MenuBar/index.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' const iconStyle = { color: 'white' } @@ -26,7 +26,7 @@ export const Menu = memo( const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { projId, apId, popId, popmassnberId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result diff --git a/src/components/Projekte/Daten/Popmassnber/index.jsx b/src/components/Projekte/Daten/Popmassnber/index.jsx index a029b4beb3..0566ab61d6 100644 --- a/src/components/Projekte/Daten/Popmassnber/index.jsx +++ b/src/components/Projekte/Daten/Popmassnber/index.jsx @@ -9,7 +9,7 @@ import { RadioButtonGroup } from '../../../shared/RadioButtonGroup.jsx' import { TextField } from '../../../shared/TextField.jsx' import { FormTitle } from '../../../shared/FormTitle/index.jsx' import { query } from './query.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { Error } from '../../../shared/Error.jsx' @@ -50,7 +50,7 @@ export const Component = memo( const client = useApolloClient() const queryClient = useQueryClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const [fieldErrors, setFieldErrors] = useState({}) @@ -131,7 +131,7 @@ export const Component = memo( } + MenuBarComponent={Menu} /> { + const store = useContext(MobxContext) + const { nodeLabelFilter } = store.tree + + const { navData, isLoading, error } = usePopmassnbersNavData() + + if (isLoading) return + + if (error) return + + return ( + + ) + }), +) diff --git a/src/components/Projekte/Daten/Popmassnbers/Menu.jsx b/src/components/Projekte/Daten/Popmassnbers/Menu.jsx index fce3faf60d..54c5aa8a8d 100644 --- a/src/components/Projekte/Daten/Popmassnbers/Menu.jsx +++ b/src/components/Projekte/Daten/Popmassnbers/Menu.jsx @@ -6,24 +6,22 @@ import { FaPlus } from 'react-icons/fa6' import IconButton from '@mui/material/IconButton' import Tooltip from '@mui/material/Tooltip' import { observer } from 'mobx-react-lite' -import { useAtom } from 'jotai' import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' +import { FilterButton } from '../../../shared/MenuBar/FilterButton.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' -import { LabelFilter, labelFilterWidth } from '../../../shared/LabelFilter.jsx' -import { listLabelFilterIsIconAtom } from '../../../../JotaiStore/index.js' +import { MobxContext } from '../../../../mobxContext.js' const iconStyle = { color: 'white' } export const Menu = memo( - observer(() => { + observer(({ toggleFilterInput }) => { const { search } = useLocation() const navigate = useNavigate() const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { popId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result @@ -59,14 +57,12 @@ export const Menu = memo( navigate(`./${id}${search}`) }, [client, store, tanstackQueryClient, navigate, search, popId]) - const [labelFilterIsIcon] = useAtom(listLabelFilterIsIconAtom) - return ( - + {!!toggleFilterInput && ( + + )} diff --git a/src/components/Projekte/Daten/Popmassnbers/index.jsx b/src/components/Projekte/Daten/Popmassnbers/index.jsx index 053cb7a672..e48409378d 100644 --- a/src/components/Projekte/Daten/Popmassnbers/index.jsx +++ b/src/components/Projekte/Daten/Popmassnbers/index.jsx @@ -1,47 +1,14 @@ -import { memo, useContext, useMemo } from 'react' -import { useApolloClient, gql } from '@apollo/client' -import { useQuery } from '@tanstack/react-query' -import { useParams } from 'react-router' -import { observer } from 'mobx-react-lite' +import { memo } from 'react' import { useAtom } from 'jotai' -import { StoreContext } from '../../../../storeContext.js' -import { createApsQuery } from '../../../../modules/createApsQuery.js' -import { createPopmassnbersQuery } from '../../../../modules/createPopmassnbersQuery.js' -import { List } from '../../../shared/List/index.jsx' -import { Menu } from './Menu.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Error } from '../../../shared/Error.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Pop } from '../Pop/index.jsx' +import { List } from './List.jsx' -export const Component = memo( - observer(() => { - const { popId } = useParams() - const apolloClient = useApolloClient() - const store = useContext(StoreContext) - const { popmassnberGqlFilterForTree, nodeLabelFilter } = store.tree +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) - const { data, isLoading, error } = useQuery( - createPopmassnbersQuery({ - popId, - popmassnberGqlFilterForTree, - apolloClient, - }), - ) - const popmassnbers = data?.data?.popById?.popmassnbersByPopId?.nodes ?? [] - const totalCount = data?.data?.popById?.popmassnbersCount?.totalCount ?? 0 + if (isDesktopView) return - if (isLoading) return - - if (error) return - - return ( - } - highlightSearchString={nodeLabelFilter.popmassnber} - /> - ) - }), -) + return +}) diff --git a/src/components/Projekte/Daten/Pops/List.jsx b/src/components/Projekte/Daten/Pops/List.jsx new file mode 100644 index 0000000000..d38be2577a --- /dev/null +++ b/src/components/Projekte/Daten/Pops/List.jsx @@ -0,0 +1,30 @@ +import { memo, useContext } from 'react' +import { observer } from 'mobx-react-lite' + +import { MobxContext } from '../../../../mobxContext.js' +import { usePopsNavData } from '../../../../modules/usePopsNavData.js' +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from './Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' + +export const List = memo( + observer(() => { + const store = useContext(MobxContext) + const { nodeLabelFilter } = store.tree + + const { navData, isLoading, error } = usePopsNavData() + + if (isLoading) return + + if (error) return + + return ( + + ) + }), +) diff --git a/src/components/Projekte/Daten/Pops/Menu.jsx b/src/components/Projekte/Daten/Pops/Menu.jsx index e4daa236db..3e53793523 100644 --- a/src/components/Projekte/Daten/Pops/Menu.jsx +++ b/src/components/Projekte/Daten/Pops/Menu.jsx @@ -13,14 +13,14 @@ import { observer } from 'mobx-react-lite' import { useAtom } from 'jotai' import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' +import { FilterButton } from '../../../shared/MenuBar/FilterButton.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { openLowerNodes } from '../../TreeContainer/openLowerNodes/index.js' import { closeLowerNodes } from '../../TreeContainer/closeLowerNodes.js' import { moveTo } from '../../../../modules/moveTo/index.js' import { copyTo } from '../../../../modules/copyTo/index.js' -import { StoreContext } from '../../../../storeContext.js' -import { LabelFilter, labelFilterWidth } from '../../../shared/LabelFilter.jsx' -import { listLabelFilterIsIconAtom } from '../../../../JotaiStore/index.js' +import { MobxContext } from '../../../../mobxContext.js' +import { hideTreeAtom } from '../../../../JotaiStore/index.js' const MoveIcon = styled(MdOutlineMoveDown)` color: white; @@ -31,13 +31,13 @@ const CopyIcon = styled(MdContentCopy)` const iconStyle = { color: 'white' } export const Menu = memo( - observer(() => { + observer(({ toggleFilterInput }) => { const { search, pathname } = useLocation() const navigate = useNavigate() const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { projId, apId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { setMoving, moving, setCopying, copying } = store const onClickAdd = useCallback(async () => { @@ -70,6 +70,9 @@ export const Menu = memo( tanstackQueryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + tanstackQueryClient.invalidateQueries({ + queryKey: [`treeAp`], + }) const id = result?.data?.createPop?.pop?.id navigate(`./${id}${search}`) }, [apId, client, store, tanstackQueryClient, navigate, search]) @@ -134,31 +137,35 @@ export const Menu = memo( }) }, [setCopying]) - const [labelFilterIsIcon] = useAtom(listLabelFilterIsIconAtom) + const [hideTree] = useAtom(hideTreeAtom) return ( - + {!!toggleFilterInput && ( + + )} - - - - - - - - - - + {!hideTree && ( + + + + + + )} + {!hideTree && ( + + + + + + )} {isMovingPop && ( { - const { apId } = useParams() - const apolloClient = useApolloClient() - const store = useContext(StoreContext) - const { popGqlFilterForTree, nodeLabelFilter } = store.tree +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) - const { data, isLoading, error } = useQuery( - createPopsQuery({ - apId, - popGqlFilterForTree, - apolloClient, - }), - ) - const pops = data?.data?.apById?.popsByApId?.nodes ?? [] - const totalCount = data?.data?.apById?.popsCount?.totalCount ?? 0 + if (isDesktopView) return - if (isLoading) return - - if (error) return - - return ( - } - highlightSearchString={nodeLabelFilter.pop} - /> - ) - }), -) + return +}) diff --git a/src/components/Projekte/Daten/Projects/index.jsx b/src/components/Projekte/Daten/Projects/index.jsx index d789978757..d3931efb7d 100644 --- a/src/components/Projekte/Daten/Projects/index.jsx +++ b/src/components/Projekte/Daten/Projects/index.jsx @@ -1,18 +1,6 @@ import { memo } from 'react' import { List } from '../../../shared/List/index.jsx' +import { navData } from '../../../Bookmarks/NavTo/Navs/Projects.jsx' -const items = [ - { - id: 'e57f56f4-4376-11e8-ab21-4314b6749d13', - label: `AP Flora Kt. Zürich`, - }, -] - -export const Component = memo(() => ( - -)) +export const Component = memo(() => ) diff --git a/src/components/Projekte/Daten/Projekt/List.jsx b/src/components/Projekte/Daten/Projekt/List.jsx new file mode 100644 index 0000000000..9e35c50323 --- /dev/null +++ b/src/components/Projekte/Daten/Projekt/List.jsx @@ -0,0 +1,16 @@ +import { memo } from 'react' + +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' +import { useProjektNavData } from '../../../../modules/useProjektNavData.js' + +export const List = memo(() => { + const { navData, isLoading, error } = useProjektNavData() + + if (isLoading) return + + if (error) return + + return +}) diff --git a/src/components/Projekte/Daten/Projekt/Projekt.jsx b/src/components/Projekte/Daten/Projekt/Projekt.jsx new file mode 100644 index 0000000000..effc025c67 --- /dev/null +++ b/src/components/Projekte/Daten/Projekt/Projekt.jsx @@ -0,0 +1,125 @@ +import { memo, useCallback, useContext, useMemo, useState } from 'react' +import styled from '@emotion/styled' +import { observer } from 'mobx-react-lite' +import { useApolloClient, useQuery, gql } from '@apollo/client' +import { useParams } from 'react-router' +import { useQueryClient } from '@tanstack/react-query' + +import { TextField } from '../../../shared/TextField.jsx' +import { FormTitle } from '../../../shared/FormTitle/index.jsx' +import { query } from './query.js' +import { MobxContext } from '../../../../mobxContext.js' +import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' +import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' +import { Error } from '../../../shared/Error.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' + +const Container = styled.div` + flex-grow: 1; + display: flex; + flex-direction: column; + overflow: hidden; +` +const FormContainer = styled.div` + display: flex; + flex-direction: column; + flex-grow: 1; + overflow: hidden; + overflow-y: auto; + scrollbar-width: thin; + padding: 10px; +` + +const fieldTypes = { + name: 'String', +} + +export const Component = memo( + observer(() => { + const { projId } = useParams() + + const queryClient = useQueryClient() + const client = useApolloClient() + const store = useContext(MobxContext) + + const [fieldErrors, setFieldErrors] = useState({}) + + const { data, loading, error } = useQuery(query, { + variables: { + id: projId, + }, + }) + + const row = useMemo(() => data?.projektById ?? {}, [data?.projektById]) + + const saveToDb = useCallback( + async (event) => { + const field = event.target.name + const value = ifIsNumericAsNumber(event.target.value) + + const variables = { + id: row.id, + [field]: value, + changedBy: store.user.name, + } + try { + await client.mutate({ + mutation: gql` + mutation updateProjekt( + $id: UUID! + $${field}: ${fieldTypes[field]} + $changedBy: String + ) { + updateProjektById( + input: { + id: $id + projektPatch: { + ${field}: $${field} + changedBy: $changedBy + } + } + ) { + projekt { + id + name + changedBy + } + } + } + `, + variables, + }) + } catch (error) { + return setFieldErrors({ [field]: error.message }) + } + setFieldErrors({}) + queryClient.invalidateQueries({ + queryKey: [`treeRoot`], + }) + }, + [client, queryClient, row.id, store.user.name], + ) + + if (loading) return + + if (error) return + + return ( + + + + + + + + + ) + }), +) diff --git a/src/components/Projekte/Daten/Projekt/index.jsx b/src/components/Projekte/Daten/Projekt/index.jsx index dac5c2d2f2..1f236a0465 100644 --- a/src/components/Projekte/Daten/Projekt/index.jsx +++ b/src/components/Projekte/Daten/Projekt/index.jsx @@ -1,125 +1,14 @@ -import { memo, useCallback, useContext, useMemo, useState } from 'react' -import styled from '@emotion/styled' -import { observer } from 'mobx-react-lite' -import { useApolloClient, useQuery, gql } from '@apollo/client' -import { useParams } from 'react-router' -import { useQueryClient } from '@tanstack/react-query' +import { memo } from 'react' +import { useAtom } from 'jotai' -import { TextField } from '../../../shared/TextField.jsx' -import { FormTitle } from '../../../shared/FormTitle/index.jsx' -import { query } from './query.js' -import { StoreContext } from '../../../../storeContext.js' -import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' -import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { Error } from '../../../shared/Error.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Projekt } from './Projekt.jsx' +import { List } from './List.jsx' -const Container = styled.div` - flex-grow: 1; - display: flex; - flex-direction: column; - overflow: hidden; -` -const FormContainer = styled.div` - display: flex; - flex-direction: column; - flex-grow: 1; - overflow: hidden; - overflow-y: auto; - scrollbar-width: thin; - padding: 10px; -` +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) -const fieldTypes = { - name: 'String', -} + if (isDesktopView) return -export const Component = memo( - observer(() => { - const { projId } = useParams() - - const queryClient = useQueryClient() - const client = useApolloClient() - const store = useContext(StoreContext) - - const [fieldErrors, setFieldErrors] = useState({}) - - const { data, loading, error } = useQuery(query, { - variables: { - id: projId, - }, - }) - - const row = useMemo(() => data?.projektById ?? {}, [data?.projektById]) - - const saveToDb = useCallback( - async (event) => { - const field = event.target.name - const value = ifIsNumericAsNumber(event.target.value) - - const variables = { - id: row.id, - [field]: value, - changedBy: store.user.name, - } - try { - await client.mutate({ - mutation: gql` - mutation updateProjekt( - $id: UUID! - $${field}: ${fieldTypes[field]} - $changedBy: String - ) { - updateProjektById( - input: { - id: $id - projektPatch: { - ${field}: $${field} - changedBy: $changedBy - } - } - ) { - projekt { - id - name - changedBy - } - } - } - `, - variables, - }) - } catch (error) { - return setFieldErrors({ [field]: error.message }) - } - setFieldErrors({}) - queryClient.invalidateQueries({ - queryKey: [`treeRoot`], - }) - }, - [client, queryClient, row.id, store.user.name], - ) - - if (loading) return - - if (error) return - - return ( - - - - - - - - - ) - }), -) + return +}) diff --git a/src/components/Projekte/Daten/Qk/Choose/index.jsx b/src/components/Projekte/Daten/Qk/Choose/index.jsx index 387f01631b..6dcd61cecd 100644 --- a/src/components/Projekte/Daten/Qk/Choose/index.jsx +++ b/src/components/Projekte/Daten/Qk/Choose/index.jsx @@ -5,12 +5,13 @@ import InputLabel from '@mui/material/InputLabel' import FormControl from '@mui/material/FormControl' import { useQuery } from '@apollo/client' import CircularProgress from '@mui/material/CircularProgress' -import { useParams } from 'react-router' +import { Form, useParams } from 'react-router' import { query } from './query.js' import { Row } from './Row/index.jsx' import { ErrorBoundary } from '../../../../shared/ErrorBoundary.jsx' import { Error } from '../../../../shared/Error.jsx' +import { FormTitle } from '../../../../shared/FormTitle/index.jsx' const Container = styled.div` height: 100%; @@ -72,6 +73,7 @@ export const Component = ({ refetchTab }) => { if (error) return return ( + return ( + { const { apId } = useParams() - const { data, loading, error, refetch } = useQuery(query, { + const { data, loading, error } = useQuery(query, { variables: { apId }, fetchPolicy: 'no-cache', }) @@ -30,20 +30,19 @@ export const Component = memo(() => { ]), ) - const qkCount = loading ? '...' : data?.allQks?.totalCount + const qkCount = data?.allQks?.totalCount if (error) return + if (loading) return + return ( - {loading ? - - : - } + ) }) diff --git a/src/components/Projekte/Daten/QkRouter/index.jsx b/src/components/Projekte/Daten/QkRouter/index.jsx deleted file mode 100644 index d5ee8b9aab..0000000000 --- a/src/components/Projekte/Daten/QkRouter/index.jsx +++ /dev/null @@ -1,94 +0,0 @@ -import { useCallback, Suspense } from 'react' -import Tabs from '@mui/material/Tabs' -import Tab from '@mui/material/Tab' -import styled from '@emotion/styled' -import { useQuery } from '@apollo/client' -import { useParams, Outlet, useNavigate, useLocation } from 'react-router' - -import { FormTitle } from '../../../shared/FormTitle/index.jsx' -import { query } from './query.js' -import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' - -const Container = styled.div` - flex-grow: 1; - display: flex; - flex-direction: column; - overflow: hidden; - background-color: ${(props) => (props.showfilter ? '#ffd3a7' : 'unset')}; -` -const StyledTab = styled(Tab)` - text-transform: none !important; -` -const TabContent = styled.div` - display: flex; - flex-direction: column; - overflow: hidden; - flex-grow: 1; - overflow: hidden; - overflow-y: auto; - scrollbar-width: thin; - fieldset { - padding-right: 30px; - } -` - -export const Component = () => { - const { apId } = useParams() - const navigate = useNavigate() - const { pathname, search } = useLocation() - - const { data, loading, error, refetch } = useQuery(query, { - variables: { apId }, - fetchPolicy: 'no-cache', - }) - - const qkCount = loading ? '...' : data?.allQks?.totalCount - const apqkCount = loading ? '...' : data?.allApqks?.totalCount - - const onChangeTab = useCallback( - (event, value) => - pathname.endsWith(apId) ? - navigate(`./${value}${search}`) - : navigate(`${value}${search}`), - [pathname, apId, navigate, search], - ) - const path = pathname.split('/').filter((el) => !!el) - const lastPathEl = path.at(-1) - - return ( - - - - - - - - - }> - - - - - - ) -} diff --git a/src/components/Projekte/Daten/QkRouter/query.js b/src/components/Projekte/Daten/QkRouter/query.js deleted file mode 100644 index a92d0dd06b..0000000000 --- a/src/components/Projekte/Daten/QkRouter/query.js +++ /dev/null @@ -1,25 +0,0 @@ -import { gql } from '@apollo/client' - -import { qk, apqk } from '../../../shared/fragments.js' - -export const query = gql` - query QkQueryForQkTopRouter($apId: UUID!) { - allQks(orderBy: [SORT_ASC, NAME_ASC]) { - totalCount - nodes { - ...QkFields - #apqksByQkName(filter: { apId: { equalTo: $apId } }) { - # totalCount - #} - } - } - allApqks(filter: { apId: { equalTo: $apId } }) { - totalCount - nodes { - ...ApqkFields - } - } - } - ${qk} - ${apqk} -` diff --git a/src/components/Projekte/Daten/Root/index.jsx b/src/components/Projekte/Daten/Root/index.jsx index 7f7e329442..35f9ece129 100644 --- a/src/components/Projekte/Daten/Root/index.jsx +++ b/src/components/Projekte/Daten/Root/index.jsx @@ -1,75 +1,16 @@ -import { memo, useMemo } from 'react' -import { useApolloClient, gql } from '@apollo/client' -import { useQuery } from '@tanstack/react-query' +import { memo } from 'react' -import { createCurrentissuesQuery } from '../../../../modules/createCurrentissuesQuery.js' import { List } from '../../../shared/List/index.jsx' import { Spinner } from '../../../shared/Spinner.jsx' import { Error } from '../../../shared/Error.jsx' +import { useRootNavData } from '../../../../modules/useRootNavData.js' export const Component = memo(() => { - const apolloClient = useApolloClient() - - const { data, isLoading, error } = useQuery({ - queryKey: ['treeRoot'], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeRootQueryForRootForm { - allUsers { - totalCount - } - allMessages { - totalCount - } - allCurrentissues { - totalCount - } - } - `, - fetchPolicy: 'no-cache', - }), - }) - const totalCount = 5 - const usersCount = data?.data?.allUsers?.totalCount ?? 0 - const messagesCount = data?.data?.allMessages?.totalCount ?? 0 - const currentIssuesCount = data?.data?.allCurrentissues?.totalCount ?? 0 - - const items = useMemo( - () => [ - { - id: 'Projekte', - label: `Projekte`, - }, - { - id: 'Benutzer', - label: `Benutzer (${usersCount})`, - }, - { - id: 'Werte-Listen', - label: `Werte-Listen (4)`, - }, - { - id: 'Mitteilungen', - label: `Mitteilungen (${messagesCount})`, - }, - { - id: 'Aktuelle-Fehler', - label: `Aktuelle Fehler (${currentIssuesCount})`, - }, - ], - [usersCount, messagesCount, currentIssuesCount], - ) + const { navData, isLoading, error } = useRootNavData() if (isLoading) return if (error) return - return ( - - ) + return }) diff --git a/src/components/Projekte/Daten/Tpop/Dateien.jsx b/src/components/Projekte/Daten/Tpop/Dateien.jsx index 196d3d8379..0470ab3b4f 100644 --- a/src/components/Projekte/Daten/Tpop/Dateien.jsx +++ b/src/components/Projekte/Daten/Tpop/Dateien.jsx @@ -2,14 +2,18 @@ import { memo } from 'react' import { useParams } from 'react-router' import { FilesRouter } from '../../../shared/Files/index.jsx' +import { FormTitle } from '../../../shared/FormTitle/index.jsx' export const Component = memo(() => { const { tpopId } = useParams() return ( - + <> + + + ) }) diff --git a/src/components/Projekte/Daten/Tpop/Historien.jsx b/src/components/Projekte/Daten/Tpop/Historien.jsx index 744a5b9564..26b2281bde 100644 --- a/src/components/Projekte/Daten/Tpop/Historien.jsx +++ b/src/components/Projekte/Daten/Tpop/Historien.jsx @@ -6,6 +6,7 @@ import { useParams } from 'react-router' import { Spinner } from '../../../shared/Spinner.jsx' import { History as SharedHistory } from '../../../shared/History/index.jsx' import { appBaseUrl } from '../../../../modules/appBaseUrl.js' +import { FormTitle } from '../../../shared/FormTitle/index.jsx' const query = gql` query tpopHistoryQuery($tpopId: UUID!) { @@ -135,7 +136,7 @@ const Container = styled.div` overflow: hidden; overflow-y: auto; scrollbar-width: thin; - padding: 8px 25px 0 25px; + padding: 10px; height: 100%; ` const ErrorContainer = styled.div` @@ -147,6 +148,9 @@ const DocLink = styled.span` ` const DocLine = styled.p` margin-bottom: 0; + &:first-of-type { + margin-top: 0; + } ` const Aenderung = styled.span` background-color: rgba(216, 67, 21, 0.2); @@ -183,178 +187,182 @@ export const Component = () => { } return ( - - - Jährlich historisierte Daten der Teil-Population ( - Dokumentation - ). - - - Änderungen zum{' '} - aktuellen Zustand sind hervorgehoben. - - {rows.map((r) => { - const dataArray = [ - { - valueInRow: row?.popByPopId?.label ?? row?.popId, - valueInHist: r?.popByPopId?.label ?? r?.popId, - label: 'Population', - }, - { - valueInRow: row?.nr, - valueInHist: r?.nr, - label: 'Nr.', - }, - { - valueInRow: row?.flurname, - valueInHist: r?.flurname, - label: 'Flurname', - }, - { - valueInRow: row?.bekanntSeit, - valueInHist: r?.bekanntSeit, - label: 'bekannt seit', - }, - { - valueInRow: row?.popStatusWerteByStatus?.text ?? row?.status, - valueInHist: r?.popStatusWerteByStatus?.text ?? r?.status, - label: 'Status', - }, - { - valueInRow: row?.statusUnklar, - valueInHist: r?.statusUnklar, - label: 'Status unklar', - }, - { - valueInRow: row?.statusUnklarGrund, - valueInHist: r?.statusUnklarGrund, - label: 'Begründung (für Status unklar)', - }, - { - valueInRow: row?.apberRelevant, - valueInHist: r?.apberRelevant, - label: 'Für AP-Bericht relevant', - }, - { - valueInRow: - row?.tpopApberrelevantGrundWerteByApberRelevantGrund?.text ?? - row?.apberRelevantGrund, - valueInHist: - r?.tpopApberrelevantGrundWerteByApberRelevantGrund?.text ?? - r?.apberRelevantGrund, - label: 'Grund für AP-Bericht (Nicht-)Relevanz', - }, - { - valueInRow: row?.geomPoint?.x, - valueInHist: r?.geomPoint?.x, - label: 'Längengrad', - }, - { - valueInRow: row?.geomPoint?.y, - valueInHist: r?.geomPoint?.y, - label: 'Breitengrad', - }, - { - valueInRow: row?.gemeinde, - valueInHist: r?.gemeinde, - label: 'Gemeinde', - }, - { - valueInRow: row?.radius, - valueInHist: r?.radius, - label: 'Radius (m)', - }, - { - valueInRow: row?.hoehe, - valueInHist: r?.hoehe, - label: 'Höhe (m.ü.M.)', - }, - { - valueInRow: row?.exposition, - valueInHist: r?.exposition, - label: 'Exposition, Besonnung', - }, - { - valueInRow: row?.klima, - valueInHist: r?.klima, - label: 'Klima', - }, - { - valueInRow: row?.neigung, - valueInHist: r?.neigung, - label: 'Hangneigung', - }, - { - valueInRow: row?.beschreibung, - valueInHist: r?.beschreibung, - label: 'Beschreibung', - }, - { - valueInRow: row?.katasterNr, - valueInHist: r?.katasterNr, - label: 'Kataster-Nr.', - }, - { - valueInRow: row?.eigentuemer, - valueInHist: r?.eigentuemer, - label: 'EigentümerIn', - }, - { - valueInRow: row?.kontakt, - valueInHist: r?.kontakt, - label: 'Kontakt vor Ort', - }, - { - valueInRow: row?.nutzungszone, - valueInHist: r?.nutzungszone, - label: 'Nutzungszone', - }, - { - valueInRow: row?.bewirtschafter, - valueInHist: r?.bewirtschafter, - label: 'BewirtschafterIn', - }, - { - valueInRow: row?.bewirtschaftung, - valueInHist: r?.bewirtschaftung, - label: 'Bewirtschaftung', - }, - { - valueInRow: row?.bemerkungen, - valueInHist: r?.bemerkungen, - label: 'Bemerkungen', - }, - { - valueInRow: row?.ekfrequenzByEkfrequenz?.code ?? row?.ekfrequenz, - valueInHist: r?.ekfrequenzByEkfrequenz?.code ?? r?.ekfrequenz, - label: 'EK-Frequenz', - }, - { - valueInRow: row?.ekfrequenzAbweichend, - valueInHist: r?.ekfrequenzAbweichend, - label: 'EK-Frequenz abweichend', - }, - { - valueInRow: row?.ekfrequenzStartjahr, - valueInHist: r?.ekfrequenzStartjahr, - label: 'EK-Frequenz Startjahr', - }, - { - valueInRow: - row?.adresseByEkfKontrolleur?.name ?? row?.ekfKontrolleur, - valueInHist: r?.adresseByEkfKontrolleur?.name ?? r?.ekfKontrolleur, - label: 'EKF-KontrolleurIn', - }, - ] + <> + + + + Jährlich historisierte Daten der Teil-Population ( + Dokumentation + ). + + + Änderungen zum{' '} + aktuellen Zustand sind hervorgehoben. + + {rows.map((r) => { + const dataArray = [ + { + valueInRow: row?.popByPopId?.label ?? row?.popId, + valueInHist: r?.popByPopId?.label ?? r?.popId, + label: 'Population', + }, + { + valueInRow: row?.nr, + valueInHist: r?.nr, + label: 'Nr.', + }, + { + valueInRow: row?.flurname, + valueInHist: r?.flurname, + label: 'Flurname', + }, + { + valueInRow: row?.bekanntSeit, + valueInHist: r?.bekanntSeit, + label: 'bekannt seit', + }, + { + valueInRow: row?.popStatusWerteByStatus?.text ?? row?.status, + valueInHist: r?.popStatusWerteByStatus?.text ?? r?.status, + label: 'Status', + }, + { + valueInRow: row?.statusUnklar, + valueInHist: r?.statusUnklar, + label: 'Status unklar', + }, + { + valueInRow: row?.statusUnklarGrund, + valueInHist: r?.statusUnklarGrund, + label: 'Begründung (für Status unklar)', + }, + { + valueInRow: row?.apberRelevant, + valueInHist: r?.apberRelevant, + label: 'Für AP-Bericht relevant', + }, + { + valueInRow: + row?.tpopApberrelevantGrundWerteByApberRelevantGrund?.text ?? + row?.apberRelevantGrund, + valueInHist: + r?.tpopApberrelevantGrundWerteByApberRelevantGrund?.text ?? + r?.apberRelevantGrund, + label: 'Grund für AP-Bericht (Nicht-)Relevanz', + }, + { + valueInRow: row?.geomPoint?.x, + valueInHist: r?.geomPoint?.x, + label: 'Längengrad', + }, + { + valueInRow: row?.geomPoint?.y, + valueInHist: r?.geomPoint?.y, + label: 'Breitengrad', + }, + { + valueInRow: row?.gemeinde, + valueInHist: r?.gemeinde, + label: 'Gemeinde', + }, + { + valueInRow: row?.radius, + valueInHist: r?.radius, + label: 'Radius (m)', + }, + { + valueInRow: row?.hoehe, + valueInHist: r?.hoehe, + label: 'Höhe (m.ü.M.)', + }, + { + valueInRow: row?.exposition, + valueInHist: r?.exposition, + label: 'Exposition, Besonnung', + }, + { + valueInRow: row?.klima, + valueInHist: r?.klima, + label: 'Klima', + }, + { + valueInRow: row?.neigung, + valueInHist: r?.neigung, + label: 'Hangneigung', + }, + { + valueInRow: row?.beschreibung, + valueInHist: r?.beschreibung, + label: 'Beschreibung', + }, + { + valueInRow: row?.katasterNr, + valueInHist: r?.katasterNr, + label: 'Kataster-Nr.', + }, + { + valueInRow: row?.eigentuemer, + valueInHist: r?.eigentuemer, + label: 'EigentümerIn', + }, + { + valueInRow: row?.kontakt, + valueInHist: r?.kontakt, + label: 'Kontakt vor Ort', + }, + { + valueInRow: row?.nutzungszone, + valueInHist: r?.nutzungszone, + label: 'Nutzungszone', + }, + { + valueInRow: row?.bewirtschafter, + valueInHist: r?.bewirtschafter, + label: 'BewirtschafterIn', + }, + { + valueInRow: row?.bewirtschaftung, + valueInHist: r?.bewirtschaftung, + label: 'Bewirtschaftung', + }, + { + valueInRow: row?.bemerkungen, + valueInHist: r?.bemerkungen, + label: 'Bemerkungen', + }, + { + valueInRow: row?.ekfrequenzByEkfrequenz?.code ?? row?.ekfrequenz, + valueInHist: r?.ekfrequenzByEkfrequenz?.code ?? r?.ekfrequenz, + label: 'EK-Frequenz', + }, + { + valueInRow: row?.ekfrequenzAbweichend, + valueInHist: r?.ekfrequenzAbweichend, + label: 'EK-Frequenz abweichend', + }, + { + valueInRow: row?.ekfrequenzStartjahr, + valueInHist: r?.ekfrequenzStartjahr, + label: 'EK-Frequenz Startjahr', + }, + { + valueInRow: + row?.adresseByEkfKontrolleur?.name ?? row?.ekfKontrolleur, + valueInHist: + r?.adresseByEkfKontrolleur?.name ?? r?.ekfKontrolleur, + label: 'EKF-KontrolleurIn', + }, + ] - return ( - - ) - })} - + return ( + + ) + })} + + ) } diff --git a/src/components/Projekte/Daten/Tpop/List.jsx b/src/components/Projekte/Daten/Tpop/List.jsx new file mode 100644 index 0000000000..7ab41e94b8 --- /dev/null +++ b/src/components/Projekte/Daten/Tpop/List.jsx @@ -0,0 +1,23 @@ +import { memo } from 'react' + +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from './Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' +import { useTpopNavData } from '../../../../modules/useTpopNavData.js' + +export const List = memo(() => { + const { navData, isLoading, error } = useTpopNavData() + + if (isLoading) return + + if (error) return + + return ( + + ) +}) diff --git a/src/components/Projekte/Daten/TpopRouter/Menu.jsx b/src/components/Projekte/Daten/Tpop/Menu.jsx similarity index 92% rename from src/components/Projekte/Daten/TpopRouter/Menu.jsx rename to src/components/Projekte/Daten/Tpop/Menu.jsx index e94c638059..4b571d779d 100644 --- a/src/components/Projekte/Daten/TpopRouter/Menu.jsx +++ b/src/components/Projekte/Daten/Tpop/Menu.jsx @@ -24,20 +24,21 @@ import Tooltip from '@mui/material/Tooltip' import isEqual from 'lodash/isEqual' import uniq from 'lodash/uniq' import styled from '@emotion/styled' +import { useAtom } from 'jotai' import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' import { openLowerNodes } from '../../TreeContainer/openLowerNodes/index.js' import { closeLowerNodes } from '../../TreeContainer/closeLowerNodes.js' -import { isMobilePhone } from '../../../../modules/isMobilePhone.js' -import { useSearchParamsState } from '../../../../modules/useSearchParamsState.js' +import { useProjekteTabs } from '../../../../modules/useProjekteTabs.js' import { moveTo } from '../../../../modules/moveTo/index.js' import { copyTo } from '../../../../modules/copyTo/index.js' import { copyTpopKoordToPop } from '../../../../modules/copyTpopKoordToPop/index.js' import { showCoordOfTpopOnMapGeoAdminCh } from '../../../../modules/showCoordOfTpopOnMapGeoAdminCh.js' import { showCoordOfTpopOnMapsZhCh } from '../../../../modules/showCoordOfTpopOnMapsZhCh.js' +import { hideTreeAtom } from '../../../../JotaiStore/index.js' // unfortunately, toggle buttons are different from icon buttons... const RoundToggleButton = styled(ToggleButton)` @@ -94,7 +95,7 @@ export const Menu = memo( const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { projId, apId, popId, tpopId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { setIdOfTpopBeingLocalized, idOfTpopBeingLocalized, @@ -111,7 +112,7 @@ export const Menu = memo( try { result = await client.mutate({ mutation: gql` - mutation createTpopForTpopRouterForm($popId: UUID!) { + mutation createTpopForTpopForm($popId: UUID!) { createTpop(input: { tpop: { popId: $popId } }) { tpop { id @@ -138,9 +139,12 @@ export const Menu = memo( tanstackQueryClient.invalidateQueries({ queryKey: [`treePopFolders`], }) + tanstackQueryClient.invalidateQueries({ + queryKey: [`treePop`], + }) const id = result?.data?.createTpop?.tpop?.id navigate( - `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Teil-Populationen/${id}${search}`, + `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Teil-Populationen/${id}/Teil-Population${search}`, ) }, [ apId, @@ -195,6 +199,9 @@ export const Menu = memo( tanstackQueryClient.invalidateQueries({ queryKey: [`treePopFolders`], }) + tanstackQueryClient.invalidateQueries({ + queryKey: [`treePop`], + }) // navigate to parent navigate( `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Teil-Populationen${search}`, @@ -242,10 +249,7 @@ export const Menu = memo( }) }, [projId, apId, popId, tpopId, store, search]) - const [projekteTabs, setProjekteTabs] = useSearchParamsState( - 'projekteTabs', - isMobilePhone() ? ['tree'] : ['tree', 'daten'], - ) + const [projekteTabs, setProjekteTabs] = useProjekteTabs() const showMapIfNotYetVisible = useCallback( (projekteTabs) => { const isVisible = projekteTabs.includes('karte') @@ -428,6 +432,8 @@ export const Menu = memo( }) }, [tpopId, client, store, tanstackQueryClient]) + const [hideTree] = useAtom(hideTreeAtom) + // ISSUE: refs are sometimes/often not set on first render // trying to measure widths of menus leads to complete chaos // so passing in static widths instead @@ -435,7 +441,7 @@ export const Menu = memo( return ( @@ -450,16 +456,20 @@ export const Menu = memo( - - - - - - - - - - + {!hideTree && ( + + + + + + )} + {!hideTree && ( + + + + + + )} { + const { tpopId } = useParams() + const store = useContext(MobxContext) + const { enqueNotification } = store + const client = useApolloClient() + const queryClient = useQueryClient() + + const { + data, + loading, + error, + refetch: refetchTpop, + } = useQuery(query, { + variables: { + id: tpopId, + }, + }) + + const apJahr = data?.tpopById?.popByPopId?.apByApId?.startJahr ?? null + + const row = data?.tpopById ?? {} + + const { + data: dataLists, + loading: loadingLists, + error: errorLists, + } = useQuery(gql` + query TpopListsQueryForTpop { + allTpopApberrelevantGrundWertes( + orderBy: SORT_ASC + filter: { code: { isNull: false } } + ) { + nodes { + value: code + label: text + } + } + allChAdministrativeUnits( + filter: { localisedcharacterstring: { equalTo: "Gemeinde" } } + orderBy: TEXT_ASC + ) { + nodes { + value: text + label: text + } + } + } + `) + const [fieldErrors, setFieldErrors] = useState({}) + const saveToDb = useCallback( + async (event) => { + const field = event.target.name + const value = ifIsNumericAsNumber(event.target.value) + + const variables = { + id: row.id, + [field]: value, + changedBy: store.user.name, + } + try { + await client.mutate({ + mutation: gql` + mutation updateTpop${field}( + $id: UUID! + $${field}: ${fieldTypes[field]} + $changedBy: String + ) { + updateTpopById( + input: { + id: $id + tpopPatch: { + ${field}: $${field} + changedBy: $changedBy + } + } + ) { + tpop { + ...TpopFields + popStatusWerteByStatus { + ...PopStatusWerteFields + } + tpopApberrelevantGrundWerteByApberRelevantGrund { + ...TpopApberrelevantGrundWerteFields + } + popByPopId { + id + apId + } + } + } + } + ${popStatusWerte} + ${tpop} + ${tpopApberrelevantGrundWerte} + `, + variables, + // no optimistic responce as geomPoint + }) + } catch (error) { + return setFieldErrors({ [field]: error.message }) + } + // update tpop on map + if ( + (value && + ((field === 'ylv95Y' && row?.lv95X) || + (field === 'lv95X' && row?.y))) || + (!value && (field === 'ylv95Y' || field === 'lv95X')) + ) { + client.refetchQueries({ + include: ['TpopForMapQuery', 'PopForMapQuery'], + }) + } + if (Object.keys(fieldErrors).length) { + setFieldErrors({}) + } + if (['nr', 'flurname'].includes(field)) { + queryClient.invalidateQueries({ + queryKey: [`treeTpop`], + }) + } + }, + [ + client, + fieldErrors, + queryClient, + row.id, + row?.lv95X, + row?.y, + store.user.name, + ], + ) + + if (error) return + + if (loading) return + + return ( + <> + + + + + + + + + {errorLists ? +
errorLists.message
+ : + } + + {errorLists ? +
errorLists.message
+ : { + if (!row.lv95X) { + return setFieldErrors({ + gemeinde: 'Es fehlen Koordinaten', + }) + } + const geojson = row?.geomPoint?.geojson + if (!geojson) return + const geojsonParsed = JSON.parse(geojson) + if (!geojsonParsed) return + let result + try { + result = await client.query({ + // this is a hack + // see: https://github.com/graphile-contrib/postgraphile-plugin-connection-filter-postgis/issues/10 + query: gql` + query tpopGemeindeQuery { + allChAdministrativeUnits( + filter: { + geom: { containsProperly: {type: "${geojsonParsed.type}", coordinates: [${geojsonParsed.coordinates}]} }, + localisedcharacterstring: {equalTo: "Gemeinde"} + } + ) { + nodes { + # apollo wants an id for its cache + id + text + } + } + } + `, + }) + } catch (error) { + return enqueNotification({ + message: error.message, + options: { + variant: 'error', + }, + }) + } + const gemeinde = + result?.data?.allChAdministrativeUnits?.nodes?.[0]?.text ?? '' + // keep following method in case table ch_administrative_units is removed again + /*const gemeinde = await getGemeindeForKoord({ + lv95X: row.lv95X, + lv95Y: row.lv95Y, + store, + })*/ + if (gemeinde) { + const fakeEvent = { + target: { value: gemeinde, name: 'gemeinde' }, + } + //handleChange(fakeEvent) + //handleBlur(fakeEvent) + saveToDb(fakeEvent) + } + }} + saveToDb={saveToDb} + /> + } + + + + + + + + + + + + + + + + + + + + +
+ + ) + }), +) diff --git a/src/components/Projekte/Daten/Tpop/index.jsx b/src/components/Projekte/Daten/Tpop/index.jsx index ec5bd01149..89d02a00e3 100644 --- a/src/components/Projekte/Daten/Tpop/index.jsx +++ b/src/components/Projekte/Daten/Tpop/index.jsx @@ -1,498 +1,14 @@ -import { memo, useContext, useState, useCallback } from 'react' -import styled from '@emotion/styled' -import { observer } from 'mobx-react-lite' -import { useQuery, useApolloClient, gql } from '@apollo/client' -import { useQueryClient } from '@tanstack/react-query' -import { useParams, useOutletContext } from 'react-router' +import { memo } from 'react' +import { useAtom } from 'jotai' -import { TextField } from '../../../shared/TextField.jsx' -import { TextFieldWithInfo } from '../../../shared/TextFieldWithInfo.jsx' -import { MarkdownField } from '../../../shared/MarkdownField/index.jsx' -import { Status } from '../../../shared/Status.jsx' -import { SelectCreatableGemeinde } from '../../../shared/SelectCreatableGemeinde.jsx' -import { Checkbox2States } from '../../../shared/Checkbox2States.jsx' -import { RadioButtonGroupWithInfo } from '../../../shared/RadioButtonGroupWithInfo.jsx' -import { TpopAbBerRelevantInfoPopover } from '../TpopAbBerRelevantInfoPopover.jsx' -//import { getGemeindeForKoord } from '../../../../modules/getGemeindeForKoord.js' -import { constants } from '../../../../modules/constants.js' -import { StoreContext } from '../../../../storeContext.js' -import { Coordinates } from '../../../shared/Coordinates.jsx' -import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' -import { - popStatusWerte, - tpop, - tpopApberrelevantGrundWerte, -} from '../../../shared/fragments.js' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Tpop } from './Tpop.jsx' +import { List } from './List.jsx' -const Container = styled.div` - display: flex; - flex-direction: column; - flex-grow: 1; - overflow: hidden; - overflow-y: auto; - scrollbar-width: thin; - padding: 0 10px; - column-width: ${constants.columnWidth}px; -` +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) -export const fieldTypes = { - popId: 'UUID', - nr: 'Int', - gemeinde: 'String', - flurname: 'String', - radius: 'Int', - hoehe: 'Int', - exposition: 'String', - klima: 'String', - neigung: 'String', - bodenTyp: 'String', - bodenKalkgehalt: 'String', - bodenDurchlaessigkeit: 'String', - bodenHumus: 'String', - bodenNaehrstoffgehalt: 'String', - bodenAbtrag: 'String', - wasserhaushalt: 'String', - beschreibung: 'String', - katasterNr: 'String', - status: 'Int', - statusUnklarGrund: 'String', - apberRelevant: 'Boolean', - apberRelevantGrund: 'Int', - bekanntSeit: 'Int', - eigentuemer: 'String', - kontakt: 'String', - nutzungszone: 'String', - bewirtschafter: 'String', - bewirtschaftung: 'String', - ekfrequenz: 'UUID', - ekfrequenzAbweichend: 'Boolean', - ekfKontrolleur: 'UUID', - ekfrequenzStartjahr: 'Int', - bemerkungen: 'String', - statusUnklar: 'Boolean', -} + if (isDesktopView) return -export const Component = memo( - observer(() => { - const { data, refetchTpop } = useOutletContext() - const { tpopId } = useParams() - const store = useContext(StoreContext) - const { enqueNotification } = store - const client = useApolloClient() - const queryClient = useQueryClient() - - const apJahr = data?.tpopById?.popByPopId?.apByApId?.startJahr ?? null - - const row = data?.tpopById ?? {} - - const { - data: dataLists, - loading: loadingLists, - error: errorLists, - } = useQuery(gql` - query TpopListsQueryForTpop { - allTpopApberrelevantGrundWertes( - orderBy: SORT_ASC - filter: { code: { isNull: false } } - ) { - nodes { - value: code - label: text - } - } - allChAdministrativeUnits( - filter: { localisedcharacterstring: { equalTo: "Gemeinde" } } - orderBy: TEXT_ASC - ) { - nodes { - value: text - label: text - } - } - } - `) - const [fieldErrors, setFieldErrors] = useState({}) - const saveToDb = useCallback( - async (event) => { - const field = event.target.name - const value = ifIsNumericAsNumber(event.target.value) - - const variables = { - id: row.id, - [field]: value, - changedBy: store.user.name, - } - try { - await client.mutate({ - mutation: gql` - mutation updateTpop${field}( - $id: UUID! - $${field}: ${fieldTypes[field]} - $changedBy: String - ) { - updateTpopById( - input: { - id: $id - tpopPatch: { - ${field}: $${field} - changedBy: $changedBy - } - } - ) { - tpop { - ...TpopFields - popStatusWerteByStatus { - ...PopStatusWerteFields - } - tpopApberrelevantGrundWerteByApberRelevantGrund { - ...TpopApberrelevantGrundWerteFields - } - popByPopId { - id - apId - } - } - } - } - ${popStatusWerte} - ${tpop} - ${tpopApberrelevantGrundWerte} - `, - variables, - // no optimistic responce as geomPoint - }) - } catch (error) { - return setFieldErrors({ [field]: error.message }) - } - // update tpop on map - if ( - (value && - ((field === 'ylv95Y' && row?.lv95X) || - (field === 'lv95X' && row?.y))) || - (!value && (field === 'ylv95Y' || field === 'lv95X')) - ) { - client.refetchQueries({ - include: ['TpopForMapQuery', 'PopForMapQuery'], - }) - } - if (Object.keys(fieldErrors).length) { - setFieldErrors({}) - } - if (['nr', 'flurname'].includes(field)) { - queryClient.invalidateQueries({ - queryKey: [`treeTpop`], - }) - } - }, - [ - client, - fieldErrors, - queryClient, - row.id, - row?.lv95X, - row?.y, - store.user.name, - ], - ) - - return ( - - - - - - - - {errorLists ? -
errorLists.message
- : - } - - {errorLists ? -
errorLists.message
- : { - if (!row.lv95X) { - return setFieldErrors({ - gemeinde: 'Es fehlen Koordinaten', - }) - } - const geojson = row?.geomPoint?.geojson - if (!geojson) return - const geojsonParsed = JSON.parse(geojson) - if (!geojsonParsed) return - let result - try { - result = await client.query({ - // this is a hack - // see: https://github.com/graphile-contrib/postgraphile-plugin-connection-filter-postgis/issues/10 - query: gql` - query tpopGemeindeQuery { - allChAdministrativeUnits( - filter: { - geom: { containsProperly: {type: "${geojsonParsed.type}", coordinates: [${geojsonParsed.coordinates}]} }, - localisedcharacterstring: {equalTo: "Gemeinde"} - } - ) { - nodes { - # apollo wants an id for its cache - id - text - } - } - } - `, - }) - } catch (error) { - return enqueNotification({ - message: error.message, - options: { - variant: 'error', - }, - }) - } - const gemeinde = - result?.data?.allChAdministrativeUnits?.nodes?.[0]?.text ?? '' - // keep following method in case table ch_administrative_units is removed again - /*const gemeinde = await getGemeindeForKoord({ - lv95X: row.lv95X, - lv95Y: row.lv95Y, - store, - })*/ - if (gemeinde) { - const fakeEvent = { - target: { value: gemeinde, name: 'gemeinde' }, - } - //handleChange(fakeEvent) - //handleBlur(fakeEvent) - saveToDb(fakeEvent) - } - }} - saveToDb={saveToDb} - /> - } - - - - - - - - - - - - - - - - - - - - -
- ) - }), -) + return +}) diff --git a/src/components/Projekte/Daten/TpopRouter/query.js b/src/components/Projekte/Daten/Tpop/query.js similarity index 100% rename from src/components/Projekte/Daten/TpopRouter/query.js rename to src/components/Projekte/Daten/Tpop/query.js diff --git a/src/components/Projekte/Daten/TpopApberrelevantGrundWertes/List.jsx b/src/components/Projekte/Daten/TpopApberrelevantGrundWertes/List.jsx new file mode 100644 index 0000000000..76ac0c3d25 --- /dev/null +++ b/src/components/Projekte/Daten/TpopApberrelevantGrundWertes/List.jsx @@ -0,0 +1,31 @@ +import { memo, useContext } from 'react' +import { observer } from 'mobx-react-lite' + +import { MobxContext } from '../../../../mobxContext.js' +import { useTpopApberrelevantGrundWertesNavData } from '../../../../modules/useTpopApberrelevantGrundWertesNavData.js' +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from './Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' + +export const List = memo( + observer(() => { + const store = useContext(MobxContext) + const { nodeLabelFilter } = store.tree + + const { navData, isLoading, error } = + useTpopApberrelevantGrundWertesNavData() + + if (isLoading) return + + if (error) return + + return ( + + ) + }), +) diff --git a/src/components/Projekte/Daten/TpopApberrelevantGrundWertes/Menu.jsx b/src/components/Projekte/Daten/TpopApberrelevantGrundWertes/Menu.jsx index 18ba356c03..2051b7562c 100644 --- a/src/components/Projekte/Daten/TpopApberrelevantGrundWertes/Menu.jsx +++ b/src/components/Projekte/Daten/TpopApberrelevantGrundWertes/Menu.jsx @@ -10,17 +10,15 @@ import IconButton from '@mui/material/IconButton' import Tooltip from '@mui/material/Tooltip' import styled from '@emotion/styled' import { observer } from 'mobx-react-lite' -import { useAtom } from 'jotai' import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' +import { FilterButton } from '../../../shared/MenuBar/FilterButton.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' import { moveTo } from '../../../../modules/moveTo/index.js' import { copyTo } from '../../../../modules/copyTo/index.js' import { closeLowerNodes } from '../../TreeContainer/closeLowerNodes.js' -import { StoreContext } from '../../../../storeContext.js' -import { LabelFilter, labelFilterWidth } from '../../../shared/LabelFilter.jsx' -import { listLabelFilterIsIconAtom } from '../../../../JotaiStore/index.js' +import { MobxContext } from '../../../../mobxContext.js' const Fitter = styled.div` margin-top: -15px; @@ -29,13 +27,13 @@ const Fitter = styled.div` const iconStyle = { color: 'white' } export const Menu = memo( - observer(() => { + observer(({ toggleFilterInput }) => { const { search, pathname } = useLocation() const navigate = useNavigate() const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { projId, tpopApberrelevantGrundWerteId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result @@ -73,14 +71,12 @@ export const Menu = memo( navigate(`./${id}${search}`) }, [client, store, tanstackQueryClient, navigate, search]) - const [labelFilterIsIcon] = useAtom(listLabelFilterIsIconAtom) - return ( - + {!!toggleFilterInput && ( + + )} diff --git a/src/components/Projekte/Daten/TpopApberrelevantGrundWertes/index.jsx b/src/components/Projekte/Daten/TpopApberrelevantGrundWertes/index.jsx index 5e1cac82e0..caab62a1ea 100644 --- a/src/components/Projekte/Daten/TpopApberrelevantGrundWertes/index.jsx +++ b/src/components/Projekte/Daten/TpopApberrelevantGrundWertes/index.jsx @@ -1,44 +1,13 @@ -import { memo, useContext } from 'react' -import { useApolloClient, gql } from '@apollo/client' -import { useQuery } from '@tanstack/react-query' -import { observer } from 'mobx-react-lite' +import { memo } from 'react' +import { useAtom } from 'jotai' -import { StoreContext } from '../../../../storeContext.js' -import { createTpopApberrelevantGrundWertesQuery } from '../../../../modules/createTpopApberrelevantGrundWertesQuery.js' -import { List } from '../../../shared/List/index.jsx' -import { Menu } from './Menu.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Error } from '../../../shared/Error.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { List } from './List.jsx' -export const Component = memo( - observer(() => { - const apolloClient = useApolloClient() - const store = useContext(StoreContext) - const { tpopApberrelevantGrundWerteGqlFilterForTree, nodeLabelFilter } = - store.tree +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) - const { data, isLoading, error } = useQuery( - createTpopApberrelevantGrundWertesQuery({ - tpopApberrelevantGrundWerteGqlFilterForTree, - apolloClient, - }), - ) - const tpopApberrelevantGrundWertes = - data?.data?.allTpopApberrelevantGrundWertes?.nodes ?? [] - const totalCount = data?.data?.totalCount?.totalCount ?? 0 + if (isDesktopView) return null - if (isLoading) return - - if (error) return - - return ( - } - highlightSearchString={nodeLabelFilter.tpopApberrelevantGrundWerte} - /> - ) - }), -) + return +}) diff --git a/src/components/Projekte/Daten/Tpop/Ek/EkYear.jsx b/src/components/Projekte/Daten/TpopEk/EkYear.jsx similarity index 100% rename from src/components/Projekte/Daten/Tpop/Ek/EkYear.jsx rename to src/components/Projekte/Daten/TpopEk/EkYear.jsx diff --git a/src/components/Projekte/Daten/Tpop/Ek/index.jsx b/src/components/Projekte/Daten/TpopEk/index.jsx similarity index 90% rename from src/components/Projekte/Daten/Tpop/Ek/index.jsx rename to src/components/Projekte/Daten/TpopEk/index.jsx index 8952a0b8b5..5ee747986f 100644 --- a/src/components/Projekte/Daten/Tpop/Ek/index.jsx +++ b/src/components/Projekte/Daten/TpopEk/index.jsx @@ -11,23 +11,24 @@ import { useQuery, useApolloClient, gql } from '@apollo/client' import { useQueryClient } from '@tanstack/react-query' import { useParams } from 'react-router' -import { Checkbox2States } from '../../../../shared/Checkbox2States.jsx' -import { RadioButtonGroup } from '../../../../shared/RadioButtonGroup.jsx' -import { Select } from '../../../../shared/Select.jsx' -import { TextField } from '../../../../shared/TextField.jsx' -import { query } from './query.js' +import { Checkbox2States } from '../../../shared/Checkbox2States.jsx' +import { RadioButtonGroup } from '../../../shared/RadioButtonGroup.jsx' +import { Select } from '../../../shared/Select.jsx' +import { TextField } from '../../../shared/TextField.jsx' +import { query as tpopQuery } from '../Tpop/query.js' import { EkYear } from './EkYear.jsx' -import { ErrorBoundary } from '../../../../shared/ErrorBoundary.jsx' -import { Spinner } from '../../../../shared/Spinner.jsx' -import { StoreContext } from '../../../../../storeContext.js' -import { query as tpopQuery } from '../../TpopRouter/query.js' -import { ifIsNumericAsNumber } from '../../../../../modules/ifIsNumericAsNumber.js' +import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { MobxContext } from '../../../../mobxContext.js' +import { query } from './query.js' +import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' import { popStatusWerte, tpop, tpopApberrelevantGrundWerte, -} from '../../../../shared/fragments.js' -import { fieldTypes } from '../index.jsx' +} from '../../../shared/fragments.js' +import { fieldTypes } from '../Tpop/Tpop.jsx' +import { FormTitle } from '../../../shared/FormTitle/index.jsx' const Container = styled.div` display: flex; @@ -79,7 +80,7 @@ const EkplanTitle = styled.h5` export const Component = () => { const { tpopId, apId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const client = useApolloClient() const queryClient = useQueryClient() @@ -186,7 +187,6 @@ export const Component = () => { } = useQuery(query, { variables: { id: tpopId, - isEk: true, apId, }, }) @@ -223,6 +223,7 @@ export const Component = () => { return ( + diff --git a/src/components/Projekte/Daten/Tpop/Ek/query.js b/src/components/Projekte/Daten/TpopEk/query.js similarity index 63% rename from src/components/Projekte/Daten/Tpop/Ek/query.js rename to src/components/Projekte/Daten/TpopEk/query.js index 16b4982f02..fc9040092d 100644 --- a/src/components/Projekte/Daten/Tpop/Ek/query.js +++ b/src/components/Projekte/Daten/TpopEk/query.js @@ -1,17 +1,17 @@ import { gql } from '@apollo/client' -import { ekfrequenz } from '../../../../shared/fragments.js' +import { ekfrequenz } from '../../../shared/fragments.js' export const query = gql` - query TpopEkQuery($id: UUID!, $isEk: Boolean!, $apId: UUID!) { - allEkplans(filter: { tpopId: { equalTo: $id } }) @include(if: $isEk) { + query TpopEkQuery($id: UUID!, $apId: UUID!) { + allEkplans(filter: { tpopId: { equalTo: $id } }) { nodes { id jahr typ } } - allTpopkontrs(filter: { tpopId: { equalTo: $id } }) @include(if: $isEk) { + allTpopkontrs(filter: { tpopId: { equalTo: $id } }) { nodes { id jahr diff --git a/src/components/Projekte/Daten/TpopFilter/ActiveFilters.tsx b/src/components/Projekte/Daten/TpopFilter/ActiveFilters.tsx index ad73c17087..2e2cf4f285 100644 --- a/src/components/Projekte/Daten/TpopFilter/ActiveFilters.tsx +++ b/src/components/Projekte/Daten/TpopFilter/ActiveFilters.tsx @@ -3,7 +3,7 @@ import styled from '@emotion/styled' import { observer } from 'mobx-react-lite' import { useParams } from 'react-router' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' const FilterCommentTitle = styled.div` @@ -24,7 +24,7 @@ export const ActiveFilters = memo( observer(() => { const { apId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { nodeLabelFilter, diff --git a/src/components/Projekte/Daten/TpopFilter/Tabs.jsx b/src/components/Projekte/Daten/TpopFilter/Tabs.jsx index 04a5488186..d21567866e 100644 --- a/src/components/Projekte/Daten/TpopFilter/Tabs.jsx +++ b/src/components/Projekte/Daten/TpopFilter/Tabs.jsx @@ -4,7 +4,7 @@ import Tab from '@mui/material/Tab' import styled from '@emotion/styled' import { initial as tpop } from '../../../../store/Tree/DataFilter/tpop.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' const Row = styled.div`` const Title = styled.div` @@ -24,7 +24,7 @@ const StyledTab = styled(Tab)` ` export const Tabs = ({ activeTab, setActiveTab, dataFilter }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { dataFilterAddOr } = store.tree const lastFilterIsEmpty = diff --git a/src/components/Projekte/Daten/TpopFilter/Tpop.jsx b/src/components/Projekte/Daten/TpopFilter/Tpop.jsx index 23cb308594..21b2368f95 100644 --- a/src/components/Projekte/Daten/TpopFilter/Tpop.jsx +++ b/src/components/Projekte/Daten/TpopFilter/Tpop.jsx @@ -13,7 +13,7 @@ import { RadioButtonGroupWithInfo } from '../../../shared/RadioButtonGroupWithIn import { TpopAbBerRelevantInfoPopover } from '../TpopAbBerRelevantInfoPopover.jsx' //import { getGemeindeForKoord } from '../../../../modules/getGemeindeForKoord.js' import { constants } from '../../../../modules/constants.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' const Container = styled.div` height: 100%; @@ -26,7 +26,7 @@ const Container = styled.div` export const Tpop = memo( observer(({ saveToDb, fieldErrors, setFieldErrors, row, apJahr }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { enqueNotification } = store const client = useApolloClient() diff --git a/src/components/Projekte/Daten/TpopFilter/index.tsx b/src/components/Projekte/Daten/TpopFilter/index.tsx index c2ebbf0d22..c6f0b7b2ff 100644 --- a/src/components/Projekte/Daten/TpopFilter/index.tsx +++ b/src/components/Projekte/Daten/TpopFilter/index.tsx @@ -7,7 +7,7 @@ import { useQuery } from '@apollo/client' import { FilterTitle } from '../../../shared/FilterTitle.jsx' import { queryTpops } from './queryTpops.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' import { Ek } from './Ek/index.jsx' import { Tpop } from './Tpop.jsx' @@ -30,7 +30,7 @@ const StyledTab = styled(Tab)` export const TpopFilter = memo( observer(() => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { dataFilter, tpopGqlFilter, dataFilterSetValue } = store.tree diff --git a/src/components/Projekte/Daten/TpopRouter/index.jsx b/src/components/Projekte/Daten/TpopRouter/index.jsx deleted file mode 100644 index 3f4ebeb60c..0000000000 --- a/src/components/Projekte/Daten/TpopRouter/index.jsx +++ /dev/null @@ -1,123 +0,0 @@ -import { useCallback, Suspense } from 'react' -import Tabs from '@mui/material/Tabs' -import Tab from '@mui/material/Tab' -import styled from '@emotion/styled' -import { useParams, Outlet, useNavigate, useLocation } from 'react-router' -import { useQuery, useApolloClient, gql } from '@apollo/client' - -import { FormTitle } from '../../../shared/FormTitle/index.jsx' -import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Error } from '../../../shared/Error.jsx' -import { query } from './query.js' -import { Menu } from './Menu.jsx' - -const Container = styled.div` - flex-grow: 1; - display: flex; - flex-direction: column; - overflow: hidden; -` -const StyledTab = styled(Tab)` - text-transform: none !important; -` -const TabContentContainer = styled.div` - overflow-y: auto; - scrollbar-width: thin; - flex-grow: 1; - display: flex; - flex-direction: column; -` -const TabContent = styled.div` - // height: calc(100% - 48px); - flex-grow: 1; - display: flex; - flex-direction: column; -` - -export const Component = () => { - const { tpopId } = useParams() - const navigate = useNavigate() - const { pathname, search } = useLocation() - - const { - data, - loading, - error, - refetch: refetchTpop, - } = useQuery(query, { - variables: { - id: tpopId, - }, - }) - - const row = data?.tpopById ?? {} - - const onChangeTab = useCallback( - (event, value) => - pathname.endsWith(tpopId) ? - navigate(`./${value}${search}`) - : navigate(`${value}${search}`), - [pathname, tpopId, navigate, search], - ) - - return ( - - - } - /> - - - - - - - - - {loading ? - - : error ? - - : }> - - - } - - - - - ) -} diff --git a/src/components/Projekte/Daten/Tpopber/Menu.jsx b/src/components/Projekte/Daten/Tpopber/Menu.jsx index e6224d1261..9a04d9f72b 100644 --- a/src/components/Projekte/Daten/Tpopber/Menu.jsx +++ b/src/components/Projekte/Daten/Tpopber/Menu.jsx @@ -14,7 +14,7 @@ import styled from '@emotion/styled' import { MenuBar } from '../../../shared/MenuBar/index.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' const iconStyle = { color: 'white' } @@ -26,7 +26,7 @@ export const Menu = memo( const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { projId, apId, popId, tpopId, tpopberId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result @@ -56,7 +56,7 @@ export const Menu = memo( queryKey: [`treeTpopber`], }) tanstackQueryClient.invalidateQueries({ - queryKey: [`treeTpopFolders`], + queryKey: [`treeTpop`], }) const id = result?.data?.createTpopber?.tpopber?.id navigate( @@ -113,7 +113,7 @@ export const Menu = memo( queryKey: [`treeTpopber`], }) tanstackQueryClient.invalidateQueries({ - queryKey: [`treeTpopFolders`], + queryKey: [`treeTpop`], }) // navigate to parent navigate( diff --git a/src/components/Projekte/Daten/Tpopber/index.jsx b/src/components/Projekte/Daten/Tpopber/index.jsx index f21e79c16d..61b5f93076 100644 --- a/src/components/Projekte/Daten/Tpopber/index.jsx +++ b/src/components/Projekte/Daten/Tpopber/index.jsx @@ -8,7 +8,7 @@ import { useQueryClient } from '@tanstack/react-query' import { RadioButtonGroup } from '../../../shared/RadioButtonGroup.jsx' import { TextField } from '../../../shared/TextField.jsx' import { FormTitle } from '../../../shared/FormTitle/index.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { Spinner } from '../../../shared/Spinner.jsx' @@ -45,7 +45,7 @@ export const Component = memo( const client = useApolloClient() const queryClient = useQueryClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const [fieldErrors, setFieldErrors] = useState({}) @@ -135,7 +135,7 @@ export const Component = memo( } + MenuBarComponent={Menu} /> { + const store = useContext(MobxContext) + const { nodeLabelFilter } = store.tree + + const { navData, isLoading, error } = useTpopbersNavData() + + if (isLoading) return + + if (error) return + + return ( + + ) + }), +) diff --git a/src/components/Projekte/Daten/Tpopbers/Menu.jsx b/src/components/Projekte/Daten/Tpopbers/Menu.jsx index ac469d672f..25783253fd 100644 --- a/src/components/Projekte/Daten/Tpopbers/Menu.jsx +++ b/src/components/Projekte/Daten/Tpopbers/Menu.jsx @@ -6,24 +6,22 @@ import { FaPlus } from 'react-icons/fa6' import IconButton from '@mui/material/IconButton' import Tooltip from '@mui/material/Tooltip' import { observer } from 'mobx-react-lite' -import { useAtom } from 'jotai' import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' +import { FilterButton } from '../../../shared/MenuBar/FilterButton.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' -import { LabelFilter, labelFilterWidth } from '../../../shared/LabelFilter.jsx' -import { listLabelFilterIsIconAtom } from '../../../../JotaiStore/index.js' +import { MobxContext } from '../../../../mobxContext.js' const iconStyle = { color: 'white' } export const Menu = memo( - observer(() => { + observer(({ toggleFilterInput }) => { const { search } = useLocation() const navigate = useNavigate() const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { tpopId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result @@ -53,20 +51,18 @@ export const Menu = memo( queryKey: [`treeTpopber`], }) tanstackQueryClient.invalidateQueries({ - queryKey: [`treeTpopFolders`], + queryKey: [`treeTpop`], }) const id = result?.data?.createTpopber?.tpopber?.id navigate(`./${id}${search}`) }, [client, store, tanstackQueryClient, navigate, search, tpopId]) - const [labelFilterIsIcon] = useAtom(listLabelFilterIsIconAtom) - return ( - + {!!toggleFilterInput && ( + + )} diff --git a/src/components/Projekte/Daten/Tpopbers/index.jsx b/src/components/Projekte/Daten/Tpopbers/index.jsx index 59e44b7315..cbbc3bcaf2 100644 --- a/src/components/Projekte/Daten/Tpopbers/index.jsx +++ b/src/components/Projekte/Daten/Tpopbers/index.jsx @@ -1,46 +1,14 @@ -import { memo, useContext } from 'react' -import { useApolloClient, gql } from '@apollo/client' -import { useQuery } from '@tanstack/react-query' -import { observer } from 'mobx-react-lite' -import { useParams } from 'react-router' +import { memo } from 'react' +import { useAtom } from 'jotai' -import { StoreContext } from '../../../../storeContext.js' -import { createApsQuery } from '../../../../modules/createApsQuery.js' -import { createTpopbersQuery } from '../../../../modules/createTpopbersQuery.js' -import { List } from '../../../shared/List/index.jsx' -import { Menu } from './Menu.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Error } from '../../../shared/Error.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Tpop } from '../Tpop/Tpop.jsx' +import { List } from './List.jsx' -export const Component = memo( - observer(() => { - const { tpopId } = useParams() - const apolloClient = useApolloClient() - const store = useContext(StoreContext) - const { tpopberGqlFilterForTree, nodeLabelFilter } = store.tree +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) - const { data, isLoading, error } = useQuery( - createTpopbersQuery({ - tpopId, - tpopberGqlFilterForTree, - apolloClient, - }), - ) - const tpopbers = data?.data?.tpopById?.tpopbersByTpopId?.nodes ?? [] - const totalCount = data?.data?.tpopById?.tpopbersCount?.totalCount ?? 0 + if (isDesktopView) return - if (isLoading) return - - if (error) return - - return ( - } - highlightSearchString={nodeLabelFilter.tpopber} - /> - ) - }), -) + return +}) diff --git a/src/components/Projekte/Daten/Tpopfeldkontr/Biotop.jsx b/src/components/Projekte/Daten/Tpopfeldkontr/Biotop.jsx index c1dc53097a..02f95519ce 100644 --- a/src/components/Projekte/Daten/Tpopfeldkontr/Biotop.jsx +++ b/src/components/Projekte/Daten/Tpopfeldkontr/Biotop.jsx @@ -9,14 +9,15 @@ import { RadioButtonGroup } from '../../../shared/RadioButtonGroup.jsx' import { TextField } from '../../../shared/TextField.jsx' import { Select } from '../../../shared/Select.jsx' import { constants } from '../../../../modules/constants.js' -import { query } from '../TpopfeldkontrRouter/query.js' -import { StoreContext } from '../../../../storeContext.js' +import { query } from './query.js' +import { MobxContext } from '../../../../mobxContext.js' import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { Error } from '../../../shared/Error.jsx' import { tpopfeldkontr } from '../../../shared/fragments.js' import { Spinner } from '../../../shared/Spinner.jsx' -import { fieldTypes, FormContainer, Section } from './index.jsx' +import { fieldTypes, FormContainer, Section } from './Tpopfeldkontr.jsx' +import { FormTitle } from '../../../shared/FormTitle/index.jsx' export const Component = memo( observer(() => { @@ -24,7 +25,7 @@ export const Component = memo( const client = useApolloClient() const queryClient = useQueryClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const [fieldErrors, setFieldErrors] = useState({}) @@ -110,6 +111,7 @@ export const Component = memo( return ( + { const { tpopkontrId } = useParams() return ( - + <> + + + ) }) diff --git a/src/components/Projekte/Daten/Tpopfeldkontr/List.jsx b/src/components/Projekte/Daten/Tpopfeldkontr/List.jsx new file mode 100644 index 0000000000..e542350ce9 --- /dev/null +++ b/src/components/Projekte/Daten/Tpopfeldkontr/List.jsx @@ -0,0 +1,23 @@ +import { memo } from 'react' + +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from './Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' +import { useTpopfeldkontrNavData } from '../../../../modules/useTpopfeldkontrNavData.js' + +export const List = memo(() => { + const { navData, isLoading, error } = useTpopfeldkontrNavData() + + if (isLoading) return + + if (error) return + + return ( + + ) +}) diff --git a/src/components/Projekte/Daten/TpopfeldkontrRouter/Menu.jsx b/src/components/Projekte/Daten/Tpopfeldkontr/Menu.jsx similarity index 82% rename from src/components/Projekte/Daten/TpopfeldkontrRouter/Menu.jsx rename to src/components/Projekte/Daten/Tpopfeldkontr/Menu.jsx index 5df447903e..2187b07d2c 100644 --- a/src/components/Projekte/Daten/TpopfeldkontrRouter/Menu.jsx +++ b/src/components/Projekte/Daten/Tpopfeldkontr/Menu.jsx @@ -16,7 +16,7 @@ import styled from '@emotion/styled' import { MenuBar } from '../../../shared/MenuBar/index.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' import { copyTo } from '../../../../modules/copyTo/index.js' import { copyBiotopTo } from '../../../../modules/copyBiotopTo.js' @@ -35,12 +35,14 @@ const iconStyle = { color: 'white' } export const Menu = memo( observer(({ row }) => { - const { search, pathname } = useLocation() - const navigate = useNavigate() const client = useApolloClient() const tanstackQueryClient = useQueryClient() + + const { search, pathname } = useLocation() + const navigate = useNavigate() const { projId, apId, popId, tpopId, tpopkontrId } = useParams() - const store = useContext(StoreContext) + + const store = useContext(MobxContext) const { moving, setMoving, @@ -49,8 +51,10 @@ export const Menu = memo( copyingBiotop, setCopyingBiotop, } = store + const { activeNodeArray, openNodes, setOpenNodes } = store.tree const onClickAdd = useCallback(async () => { + // 1. add new tpopkontr let result try { result = await client.mutate({ @@ -77,15 +81,53 @@ export const Menu = memo( }, }) } + const id = result?.data?.createTpopkontr?.tpopkontr?.id + + // 2. add new tpopkontrzaehl + const resultZaehl = await client.mutate({ + mutation: gql` + mutation createTpokontrzaehlForTpopfeldkontrForm($parentId: UUID!) { + createTpopkontrzaehl( + input: { tpopkontrzaehl: { tpopkontrId: $parentId } } + ) { + tpopkontrzaehl { + id + } + } + } + `, + variables: { parentId: id }, + }) + + // 3. open the tpopkontrzaehl Folder + const zaehlId = + resultZaehl?.data?.createTpopkontrzaehl?.tpopkontrzaehl?.id + const activeNodeArrayWithoutLastElement = activeNodeArray.slice(0, -1) + const tpopkontrNode = [...activeNodeArrayWithoutLastElement, id] + const zaehlungenFolderNode = [...tpopkontrNode, 'Zaehlungen'] + const zaehlungNode = [...zaehlungenFolderNode, zaehlId] + const newOpenNodes = [ + ...openNodes, + tpopkontrNode, + zaehlungenFolderNode, + zaehlungNode, + ] + setOpenNodes(newOpenNodes) + + // 4. refresh tree + tanstackQueryClient.invalidateQueries({ + queryKey: [`treeTpop`], + }) tanstackQueryClient.invalidateQueries({ queryKey: [`treeTpopfeldkontr`], }) tanstackQueryClient.invalidateQueries({ - queryKey: [`treeTpopFolders`], + queryKey: [`treeTpopfeldkontrzaehl`], }) - const id = result?.data?.createTpopkontr?.tpopkontr?.id + + // 5. navigate to new tpopkontr navigate( - `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Teil-Populationen/${tpopId}/Feld-Kontrollen/${id}${search}`, + `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Teil-Populationen/${tpopId}/Feld-Kontrollen/${id}/Feld-Kontrolle${search}`, ) }, [ apId, @@ -140,7 +182,7 @@ export const Menu = memo( queryKey: [`treeTpopfeldkontr`], }) tanstackQueryClient.invalidateQueries({ - queryKey: [`treeTpopFolders`], + queryKey: [`treeTpop`], }) // navigate to parent navigate( @@ -173,7 +215,7 @@ export const Menu = memo( } setMoving({ id: tpopkontrId, - label: row.labelEk, + label: row.label, table: 'tpopfeldkontr', toTable: 'tpopfeldkontr', fromParentId: tpopId, @@ -223,7 +265,7 @@ export const Menu = memo( setCopying({ table: 'tpopfeldkontr', id: tpopkontrId, - label: row.labelEk, + label: row.labelEk ?? row.label, withNextLevel: false, }) setCopyBiotopMenuAnchorEl(null) @@ -231,27 +273,25 @@ export const Menu = memo( const onClickSetBiotopCopying = useCallback(() => { setCopyingBiotop({ id: tpopkontrId, - label: row.labelEk, + label: row.labelEk ?? row.label, }) setCopyBiotopMenuAnchorEl(null) }, [tpopkontrId, row, setCopyingBiotop]) const onClickStopCopying = useCallback(() => { - if (isCopyingTpopfeldkontr) { - return setCopying({ - table: null, - id: '99999999-9999-9999-9999-999999999999', - label: null, - withNextLevel: false, - }) - } + setCopying({ + table: null, + id: '99999999-9999-9999-9999-999999999999', + label: null, + withNextLevel: false, + }) setCopyingBiotop({ id: null, label: null }) }, [setCopying]) return ( @@ -269,7 +309,7 @@ export const Menu = memo( } - {(isCopyingTpopfeldkontr || isCopyingBiotop) && ( - + {isCopying && ( + diff --git a/src/components/Projekte/Daten/Tpopfeldkontr/Tpopfeldkontr.jsx b/src/components/Projekte/Daten/Tpopfeldkontr/Tpopfeldkontr.jsx new file mode 100644 index 0000000000..ffdb0f4932 --- /dev/null +++ b/src/components/Projekte/Daten/Tpopfeldkontr/Tpopfeldkontr.jsx @@ -0,0 +1,337 @@ +import { memo, useState, useCallback, useContext } from 'react' +import styled from '@emotion/styled' +import { observer } from 'mobx-react-lite' +import { useApolloClient, useQuery, gql } from '@apollo/client' +import { useParams, useOutletContext } from 'react-router' +import { useQueryClient } from '@tanstack/react-query' + +import { RadioButtonGroup } from '../../../shared/RadioButtonGroup.jsx' +import { TextField } from '../../../shared/TextField.jsx' +import { MarkdownField } from '../../../shared/MarkdownField/index.jsx' +import { Select } from '../../../shared/Select.jsx' +import { JesNo } from '../../../shared/JesNo.jsx' +import { Checkbox2States } from '../../../shared/Checkbox2States.jsx' +import { RadioButtonGroupWithInfo } from '../../../shared/RadioButtonGroupWithInfo.jsx' +import { DateField } from '../../../shared/Date.jsx' +import { StringToCopy } from '../../../shared/StringToCopy.jsx' +import { TpopfeldkontrentwicklungPopover } from '../TpopfeldkontrentwicklungPopover.jsx' +import { constants } from '../../../../modules/constants.js' +import { MobxContext } from '../../../../mobxContext.js' +import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' +import { tpopfeldkontr } from '../../../shared/fragments.js' +import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' +import { Error } from '../../../shared/Error.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { FormTitle } from '../../../shared/FormTitle/index.jsx' +import { Menu } from './Menu.jsx' +import { query } from './query.js' + +export const FormContainer = styled.div` + display: flex; + flex-direction: column; + flex-grow: 1; + overflow: hidden; + overflow-y: auto; + scrollbar-width: thin; + padding: 10px; + height: 100%; + column-width: ${constants.columnWidth}px; +` +export const Section = styled.div` + padding-top: 20px; + padding-bottom: 5px; + font-weight: bold; + break-after: avoid; + &:after { + content: ':'; + } +` + +export const fieldTypes = { + typ: 'String', + datum: 'Date', + jahr: 'Int', + vitalitaet: 'String', + ueberlebensrate: 'Int', + entwicklung: 'Int', + ursachen: 'String', + erfolgsbeurteilung: 'String', + umsetzungAendern: 'String', + kontrolleAendern: 'String', + bemerkungen: 'String', + lrDelarze: 'String', + flaeche: 'Int', + lrUmgebungDelarze: 'String', + vegetationstyp: 'String', + konkurrenz: 'String', + moosschicht: 'String', + krautschicht: 'String', + strauchschicht: 'String', + baumschicht: 'String', + bodenTyp: 'String', + bodenKalkgehalt: 'String', + bodenDurchlaessigkeit: 'String', + bodenHumus: 'String', + bodenNaehrstoffgehalt: 'String', + bodenAbtrag: 'String', + idealbiotopUebereinstimmung: 'Int', + handlungsbedarf: 'String', + flaecheUeberprueft: 'Int', + deckungVegetation: 'Int', + deckungNackterBoden: 'Int', + deckungApArt: 'Int', + vegetationshoeheMaximum: 'Int', + vegetationshoeheMittel: 'Int', + gefaehrdung: 'String', + tpopId: 'UUID', + bearbeiter: 'UUID', + planVorhanden: 'Boolean', + jungpflanzenVorhanden: 'Boolean', + apberNichtRelevant: 'Boolean', + apberNichtRelevantGrund: 'String', +} + +const tpopkontrTypWerte = [ + { + value: 'Ausgangszustand', + label: 'Ausgangszustand', + }, + { + value: 'Zwischenbeurteilung', + label: 'Zwischenbeurteilung', + }, +] + +export const Component = memo( + observer(() => { + const { tpopkontrId } = useParams() + + const { data, loading, error } = useQuery(query, { + variables: { id: tpopkontrId }, + }) + + const row = data?.tpopkontrById ?? {} + + const client = useApolloClient() + const queryClient = useQueryClient() + const store = useContext(MobxContext) + + const [fieldErrors, setFieldErrors] = useState({}) + + const saveToDb = useCallback( + async (event) => { + const field = event.target.name + const value = ifIsNumericAsNumber(event.target.value) + + const variables = { + id: row.id, + [field]: value, + changedBy: store.user.name, + } + if (field === 'jahr') { + variables.datum = null + } + if (field === 'datum') { + // value can be null so check if substring method exists + const newJahr = + value && value.substring ? +value.substring(0, 4) : value + variables.jahr = newJahr + } + try { + await client.mutate({ + mutation: gql` + mutation updateTpopkontrForEk( + $id: UUID! + $${field}: ${fieldTypes[field]} + ${field === 'jahr' ? '$datum: Date' : ''} + ${field === 'datum' ? '$jahr: Int' : ''} + $changedBy: String + ) { + updateTpopkontrById( + input: { + id: $id + tpopkontrPatch: { + ${field}: $${field} + ${field === 'jahr' ? 'datum: $datum' : ''} + ${field === 'datum' ? 'jahr: $jahr' : ''} + changedBy: $changedBy + } + } + ) { + tpopkontr { + ...TpopfeldkontrFields + } + } + } + ${tpopfeldkontr} + `, + variables, + }) + } catch (error) { + return setFieldErrors({ [field]: error.message }) + } + setFieldErrors({}) + if (['jahr', 'datum', 'typ'].includes(field)) { + queryClient.invalidateQueries({ + queryKey: [`treeTpopfeldkontr`], + }) + } + }, + [client, queryClient, row.id, store.user.name], + ) + + if (error) return + + if (loading) return + + return ( + + + + + + + - - - - - - - - - - - - - - - - ) - }), -) + return +}) diff --git a/src/components/Projekte/Daten/TpopfeldkontrRouter/query.js b/src/components/Projekte/Daten/Tpopfeldkontr/query.js similarity index 100% rename from src/components/Projekte/Daten/TpopfeldkontrRouter/query.js rename to src/components/Projekte/Daten/Tpopfeldkontr/query.js diff --git a/src/components/Projekte/Daten/TpopfeldkontrFilter/Tabs.jsx b/src/components/Projekte/Daten/TpopfeldkontrFilter/Tabs.jsx index 7604884152..a5ddf43029 100644 --- a/src/components/Projekte/Daten/TpopfeldkontrFilter/Tabs.jsx +++ b/src/components/Projekte/Daten/TpopfeldkontrFilter/Tabs.jsx @@ -4,7 +4,7 @@ import Tab from '@mui/material/Tab' import styled from '@emotion/styled' import { initial as tpopfeldkontr } from '../../../../store/Tree/DataFilter/tpopfeldkontr.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' const Row = styled.div`` const Title = styled.div` @@ -24,7 +24,7 @@ const StyledTab = styled(Tab)` ` export const Tabs = ({ activeTab, setActiveTab, dataFilter }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { dataFilterAddOr } = store.tree const lastFilterIsEmpty = diff --git a/src/components/Projekte/Daten/TpopfeldkontrFilter/index.jsx b/src/components/Projekte/Daten/TpopfeldkontrFilter/index.jsx index 1588ba6a06..d21f581fe2 100644 --- a/src/components/Projekte/Daten/TpopfeldkontrFilter/index.jsx +++ b/src/components/Projekte/Daten/TpopfeldkontrFilter/index.jsx @@ -18,7 +18,7 @@ import { TpopfeldkontrentwicklungPopover } from '../TpopfeldkontrentwicklungPopo import { constants } from '../../../../modules/constants.js' import { query } from './query.js' import { queryTpopkontrs } from './queryTpopkontrs.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { Error } from '../../../shared/Error.jsx' @@ -85,7 +85,7 @@ export const TpopfeldkontrFilter = memo( observer(() => { const { apId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { dataFilter, ekGqlFilter, diff --git a/src/components/Projekte/Daten/TpopfeldkontrRouter/index.jsx b/src/components/Projekte/Daten/TpopfeldkontrRouter/index.jsx deleted file mode 100644 index becb960d70..0000000000 --- a/src/components/Projekte/Daten/TpopfeldkontrRouter/index.jsx +++ /dev/null @@ -1,110 +0,0 @@ -import { useCallback, Suspense } from 'react' -import Tabs from '@mui/material/Tabs' -import Tab from '@mui/material/Tab' -import styled from '@emotion/styled' -import { useApolloClient, useQuery, gql } from '@apollo/client' -import { useParams, Outlet, useNavigate, useLocation } from 'react-router' - -import { FormTitle } from '../../../shared/FormTitle/index.jsx' -import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { Error } from '../../../shared/Error.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Menu } from './Menu.jsx' -import { query } from './query.js' - -const Container = styled.div` - flex-grow: 1; - display: flex; - flex-direction: column; - overflow: hidden; -` -const StyledTab = styled(Tab)` - text-transform: none !important; -` -const TabContentContainer = styled.div` - overflow-y: auto; - scrollbar-width: thin; - flex-grow: 1; - display: flex; - flex-direction: column; -` -const TabContent = styled.div` - flex-grow: 1; - display: flex; - flex-direction: column; -` - -export const Component = () => { - const { tpopkontrId } = useParams() - const navigate = useNavigate() - const { pathname, search } = useLocation() - - const { data, loading, error } = useQuery(query, { - variables: { - id: tpopkontrId, - }, - }) - - const row = data?.tpopkontrById ?? {} - - const onChangeTab = useCallback( - (event, value) => - pathname.endsWith(tpopkontrId) ? - navigate(`./${value}${search}`) - : navigate(`${value}${search}`), - [pathname, tpopkontrId, navigate, search], - ) - - return ( - - - } - /> - - - - - - - - {loading ? - - : error ? - - : }> - - - } - - - - - ) -} diff --git a/src/components/Projekte/Daten/Tpopfeldkontrs/List.jsx b/src/components/Projekte/Daten/Tpopfeldkontrs/List.jsx new file mode 100644 index 0000000000..30564dbae0 --- /dev/null +++ b/src/components/Projekte/Daten/Tpopfeldkontrs/List.jsx @@ -0,0 +1,30 @@ +import { memo, useContext } from 'react' +import { observer } from 'mobx-react-lite' + +import { MobxContext } from '../../../../mobxContext.js' +import { useTpopfeldkontrsNavData } from '../../../../modules/useTpopfeldkontrsNavData.js' +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from './Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' + +export const List = memo( + observer(() => { + const store = useContext(MobxContext) + const { nodeLabelFilter } = store.tree + + const { navData, isLoading, error } = useTpopfeldkontrsNavData() + + if (isLoading) return + + if (error) return + + return ( + + ) + }), +) diff --git a/src/components/Projekte/Daten/Tpopfeldkontrs/Menu.jsx b/src/components/Projekte/Daten/Tpopfeldkontrs/Menu.jsx index c99f533c27..fd32ee61df 100644 --- a/src/components/Projekte/Daten/Tpopfeldkontrs/Menu.jsx +++ b/src/components/Projekte/Daten/Tpopfeldkontrs/Menu.jsx @@ -13,14 +13,14 @@ import { observer } from 'mobx-react-lite' import { useAtom } from 'jotai' import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' +import { FilterButton } from '../../../shared/MenuBar/FilterButton.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { openLowerNodes } from '../../TreeContainer/openLowerNodes/index.js' import { closeLowerNodes } from '../../TreeContainer/closeLowerNodes.js' import { moveTo } from '../../../../modules/moveTo/index.js' import { copyTo } from '../../../../modules/copyTo/index.js' -import { StoreContext } from '../../../../storeContext.js' -import { LabelFilter, labelFilterWidth } from '../../../shared/LabelFilter.jsx' -import { listLabelFilterIsIconAtom } from '../../../../JotaiStore/index.js' +import { MobxContext } from '../../../../mobxContext.js' +import { hideTreeAtom } from '../../../../JotaiStore/index.js' const MoveIcon = styled(MdOutlineMoveDown)` color: white; @@ -31,16 +31,27 @@ const CopyIcon = styled(MdContentCopy)` const iconStyle = { color: 'white' } export const Menu = memo( - observer(() => { - const { search } = useLocation() - const navigate = useNavigate() + observer(({ toggleFilterInput }) => { const apolloClient = useApolloClient() const tanstackQueryClient = useQueryClient() + + const { search } = useLocation() + const navigate = useNavigate() const { projId, apId, popId, tpopId } = useParams() - const store = useContext(StoreContext) - const { setMoving, moving, setCopying, copying } = store + + const store = useContext(MobxContext) + const { + setMoving, + moving, + setCopying, + copying, + copyingBiotop, + setCopyingBiotop, + } = store + const { activeNodeArray, openNodes, setOpenNodes } = store.tree const onClickAdd = useCallback(async () => { + // 1. create new tpopkontr let result try { result = await apolloClient.mutate({ @@ -66,13 +77,50 @@ export const Menu = memo( }, }) } + const id = result?.data?.createTpopkontr?.tpopkontr?.id + + // 2. add new tpopkontrzaehl + const resultZaehl = await apolloClient.mutate({ + mutation: gql` + mutation createTpokontrzaehlForTpopfeldkontrs($parentId: UUID!) { + createTpopkontrzaehl( + input: { tpopkontrzaehl: { tpopkontrId: $parentId } } + ) { + tpopkontrzaehl { + id + } + } + } + `, + variables: { parentId: id }, + }) + + // 3. open the tpopkontrzaehl Folder + const zaehlId = + resultZaehl?.data?.createTpopkontrzaehl?.tpopkontrzaehl?.id + const tpopkontrNode = [...activeNodeArray, id] + const zaehlungenFolderNode = [...tpopkontrNode, 'Zaehlungen'] + const zaehlungNode = [...zaehlungenFolderNode, zaehlId] + const newOpenNodes = [ + ...openNodes, + tpopkontrNode, + zaehlungenFolderNode, + zaehlungNode, + ] + setOpenNodes(newOpenNodes) + + // 4. refresh tree tanstackQueryClient.invalidateQueries({ queryKey: [`treeTpopfeldkontr`], }) tanstackQueryClient.invalidateQueries({ - queryKey: [`treeTpopFolders`], + queryKey: [`treeTpop`], }) - const id = result?.data?.createTpopkontr?.tpopkontr?.id + tanstackQueryClient.invalidateQueries({ + queryKey: [`treeTpopfeldkontrzaehl`], + }) + + // 5. navigate to new tpopkontr navigate(`./${id}${search}`) }, [apolloClient, store, tanstackQueryClient, navigate, search, tpopId]) @@ -127,6 +175,8 @@ export const Menu = memo( }, [setMoving]) const isCopyingEk = copying.table === 'tpopfeldkontr' + const isCopyingBiotop = !!copyingBiotop.id + const isCopying = isCopyingEk || isCopyingBiotop const onClickCopyEkfToHere = useCallback(() => { return copyTo({ parentId: tpopId, @@ -143,33 +193,38 @@ export const Menu = memo( label: null, withNextLevel: false, }) - }, [setCopying]) + setCopyingBiotop({ id: null, label: null }) + }, [setCopying, setCopyingBiotop]) - const [labelFilterIsIcon] = useAtom(listLabelFilterIsIconAtom) + const [hideTree] = useAtom(hideTreeAtom) return ( - + {!!toggleFilterInput && ( + + )} - - - - - - - - - - + {!hideTree && ( + + + + + + )} + {!hideTree && ( + + + + + + )} {isMovingEk && ( @@ -191,8 +246,10 @@ export const Menu = memo( )} - {isCopyingEk && ( - + {isCopying && ( + diff --git a/src/components/Projekte/Daten/Tpopfeldkontrs/index.jsx b/src/components/Projekte/Daten/Tpopfeldkontrs/index.jsx index 13ace32618..cbbc3bcaf2 100644 --- a/src/components/Projekte/Daten/Tpopfeldkontrs/index.jsx +++ b/src/components/Projekte/Daten/Tpopfeldkontrs/index.jsx @@ -1,47 +1,14 @@ -import { memo, useContext } from 'react' -import { useApolloClient, gql } from '@apollo/client' -import { useQuery } from '@tanstack/react-query' -import { observer } from 'mobx-react-lite' -import { useParams } from 'react-router' +import { memo } from 'react' +import { useAtom } from 'jotai' -import { StoreContext } from '../../../../storeContext.js' -import { createApsQuery } from '../../../../modules/createApsQuery.js' -import { createTpopfeldkontrQuery } from '../../../../modules/createTpopfeldkontrQuery.js' -import { List } from '../../../shared/List/index.jsx' -import { Menu } from './Menu.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Error } from '../../../shared/Error.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Tpop } from '../Tpop/Tpop.jsx' +import { List } from './List.jsx' -export const Component = memo( - observer(() => { - const { tpopId } = useParams() - const apolloClient = useApolloClient() - const store = useContext(StoreContext) - const { ekGqlFilterForTree, nodeLabelFilter } = store.tree +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) - const { data, isLoading, error } = useQuery( - createTpopfeldkontrQuery({ - tpopId, - ekGqlFilterForTree, - apolloClient, - }), - ) - const tpopfeldkontrs = data?.data?.tpopById?.tpopfeldkontrs?.nodes ?? [] - const totalCount = - data?.data?.tpopById?.tpopfeldkontrsCount?.totalCount ?? 0 + if (isDesktopView) return - if (isLoading) return - - if (error) return - - return ( - } - highlightSearchString={nodeLabelFilter.tpopkontr} - /> - ) - }), -) + return +}) diff --git a/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Count/Einheit.jsx b/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Count/Einheit.jsx index c54f99f234..72b007925b 100644 --- a/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Count/Einheit.jsx +++ b/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Count/Einheit.jsx @@ -5,7 +5,7 @@ import { useApolloClient } from '@apollo/client' import { useQueryClient } from '@tanstack/react-query' import { Select } from '../../../../../shared/Select.jsx' -import { StoreContext } from '../../../../../../storeContext.js' +import { MobxContext } from '../../../../../../mobxContext.js' import { updateTpopkontrzaehlById } from './updateTpopkontrzaehlById.js' import { ifIsNumericAsNumber } from '../../../../../../modules/ifIsNumericAsNumber.js' @@ -35,7 +35,7 @@ const EinheitLabel = styled(Label)` export const Einheit = memo( observer(({ nr, row, refetch, zaehleinheitWerte }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const client = useApolloClient() const queryClient = useQueryClient() diff --git a/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Count/Geschaetzt.jsx b/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Count/Geschaetzt.jsx index 0f5afc18db..ab0b96d066 100644 --- a/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Count/Geschaetzt.jsx +++ b/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Count/Geschaetzt.jsx @@ -4,13 +4,13 @@ import { useApolloClient } from '@apollo/client' import { useQueryClient } from '@tanstack/react-query' import { TextField } from '../../../../../shared/TextField.jsx' -import { StoreContext } from '../../../../../../storeContext.js' +import { MobxContext } from '../../../../../../mobxContext.js' import { updateTpopkontrzaehlById } from './updateTpopkontrzaehlById.js' import { ifIsNumericAsNumber } from '../../../../../../modules/ifIsNumericAsNumber.js' export const Geschaetzt = memo( observer(({ row, refetch }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const client = useApolloClient() const queryClient = useQueryClient() diff --git a/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Count/Gezaehlt.jsx b/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Count/Gezaehlt.jsx index 52c2b81214..44eebed104 100644 --- a/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Count/Gezaehlt.jsx +++ b/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Count/Gezaehlt.jsx @@ -5,13 +5,13 @@ import { useApolloClient } from '@apollo/client' import { useQueryClient } from '@tanstack/react-query' import { TextField } from '../../../../../shared/TextField.jsx' -import { StoreContext } from '../../../../../../storeContext.js' +import { MobxContext } from '../../../../../../mobxContext.js' import { updateTpopkontrzaehlById } from './updateTpopkontrzaehlById.js' import { ifIsNumericAsNumber } from '../../../../../../modules/ifIsNumericAsNumber.js' export const Gezaehlt = memo( observer(({ row, refetch }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const client = useApolloClient() const queryClient = useQueryClient() diff --git a/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Count/index.jsx b/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Count/index.jsx index f2929c0f15..cb170db254 100644 --- a/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Count/index.jsx +++ b/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Count/index.jsx @@ -13,7 +13,7 @@ import { Gezaehlt } from './Gezaehlt.jsx' import { Geschaetzt } from './Geschaetzt.jsx' import { query } from './query.js' import { createTpopkontrzaehl } from './createTpopkontrzaehl.js' -import { StoreContext } from '../../../../../../storeContext.js' +import { MobxContext } from '../../../../../../mobxContext.js' import { Error } from '../../../../../shared/Error.jsx' import { Spinner } from '../../../../../shared/Spinner.jsx' @@ -176,8 +176,8 @@ export const Count = memo( const client = useApolloClient() const queryClient = useQueryClient() - const store = useContext(StoreContext) - const { setToDelete } = useContext(StoreContext) + const store = useContext(MobxContext) + const { setToDelete } = useContext(MobxContext) const { activeNodeArray } = store.tree diff --git a/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Headdata/index.jsx b/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Headdata/index.jsx index d10c6559e6..df0d5b593d 100644 --- a/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Headdata/index.jsx +++ b/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Headdata/index.jsx @@ -4,7 +4,7 @@ import { observer } from 'mobx-react-lite' import { useQuery, useApolloClient, gql } from '@apollo/client' import { Select } from '../../../../../shared/Select.jsx' -import { StoreContext } from '../../../../../../storeContext.js' +import { MobxContext } from '../../../../../../mobxContext.js' import { query } from './query.js' import { adresse as adresseFragment, @@ -81,7 +81,7 @@ const StatusLabel = styled(Label)` export const Headdata = memo( observer(({ pop, tpop, row }) => { const client = useApolloClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { user, isPrint } = store const { data, loading, error } = useQuery(query) const [errors, setErrors] = useState(null) diff --git a/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Map.jsx b/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Map.jsx index 1bca121870..e346501e7c 100644 --- a/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Map.jsx +++ b/src/components/Projekte/Daten/Tpopfreiwkontr/Form/Map.jsx @@ -3,7 +3,7 @@ import styled from '@emotion/styled' import { observer } from 'mobx-react-lite' import { RadioButton } from '../../../../shared/RadioButton.jsx' -import { StoreContext } from '../../../../../storeContext.js' +import { MobxContext } from '../../../../../mobxContext.js' const Area = styled.div` border: 1px solid rgba(0, 0, 0, 0.5); @@ -54,7 +54,7 @@ const MapVal2 = styled.div` export const Map = memo( observer(({ saveToDb, row, errors }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { isPrint } = store const onSaveFalse = useCallback(() => { diff --git a/src/components/Projekte/Daten/Tpopfreiwkontr/Form/index.jsx b/src/components/Projekte/Daten/Tpopfreiwkontr/Form/index.jsx index a5f4e646f7..5032d42a61 100644 --- a/src/components/Projekte/Daten/Tpopfreiwkontr/Form/index.jsx +++ b/src/components/Projekte/Daten/Tpopfreiwkontr/Form/index.jsx @@ -21,7 +21,7 @@ import { Files } from './Files.jsx' import { Count } from './Count/index.jsx' import { Verification } from './Verification.jsx' import { Image } from './Image.jsx' -import { StoreContext } from '../../../../../storeContext.js' +import { MobxContext } from '../../../../../mobxContext.js' import { ifIsNumericAsNumber } from '../../../../../modules/ifIsNumericAsNumber.js' import { adresse as adresseFragment, @@ -147,7 +147,7 @@ export const Form = memo( const client = useApolloClient() const queryClient = useQueryClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { isPrint, user } = store const { dataFilterSetValue } = store.tree const { token } = user @@ -475,7 +475,7 @@ export const Form = memo( errors={errors} /> )} - {!isPrint && } + {!isPrint && false && } {!isPrint && !isFreiwillig && ( { + const { navData, isLoading, error } = useTpopfreiwkontrNavData() + + if (isLoading) return + + if (error) return + + // BEWARE: Zählungen need to be hidden in this list + const navDataToPass = useMemo( + () => ({ + ...navData, + menus: navData.menus.filter((m) => !m.hideInNavList), + }), + [navData], + ) + + return ( + + ) +}) diff --git a/src/components/Projekte/Daten/Tpopfreiwkontr/Menu.jsx b/src/components/Projekte/Daten/Tpopfreiwkontr/Menu.jsx index 2d4cbde8b5..c1238cedc6 100644 --- a/src/components/Projekte/Daten/Tpopfreiwkontr/Menu.jsx +++ b/src/components/Projekte/Daten/Tpopfreiwkontr/Menu.jsx @@ -16,7 +16,7 @@ import styled from '@emotion/styled' import { MenuBar } from '../../../shared/MenuBar/index.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' import { copyTo } from '../../../../modules/copyTo/index.js' import { moveTo } from '../../../../modules/moveTo/index.js' @@ -38,7 +38,7 @@ export const Menu = memo( const client = useApolloClient() const tanstackQueryClient = useQueryClient() const { projId, apId, popId, tpopId, tpopkontrId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { moving, setMoving, copying, setCopying, setIsPrint } = store const onClickAdd = useCallback(async () => { @@ -75,7 +75,7 @@ export const Menu = memo( queryKey: [`treeTpopfreiwkontr`], }) tanstackQueryClient.invalidateQueries({ - queryKey: [`treeTpopFolders`], + queryKey: [`treeTpop`], }) const id = result?.data?.createTpopkontr?.tpopkontr?.id navigate( @@ -132,7 +132,7 @@ export const Menu = memo( queryKey: [`treeTpopfreiwkontr`], }) tanstackQueryClient.invalidateQueries({ - queryKey: [`treeTpopFolders`], + queryKey: [`treeTpop`], }) // navigate to parent navigate( diff --git a/src/components/Projekte/Daten/Tpopfreiwkontr/Tpopfreiwkontr.jsx b/src/components/Projekte/Daten/Tpopfreiwkontr/Tpopfreiwkontr.jsx new file mode 100644 index 0000000000..3bd8aca4d9 --- /dev/null +++ b/src/components/Projekte/Daten/Tpopfreiwkontr/Tpopfreiwkontr.jsx @@ -0,0 +1,166 @@ +import { memo, useEffect, useContext } from 'react' +import styled from '@emotion/styled' +import { observer } from 'mobx-react-lite' +import { useApolloClient, useQuery } from '@apollo/client' +import { useLocation, useParams } from 'react-router' + +import { query } from './query.js' +import { createTpopkontrzaehl } from './createTpopkontrzaehl.js' +import { FormTitle } from '../../../shared/FormTitle/index.jsx' +import { MobxContext } from '../../../../mobxContext.js' +import { Error } from '../../../shared/Error.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Form } from './Form/index.jsx' +import { Menu } from './Menu.jsx' + +const Container = styled.div` + flex-grow: 0; + display: flex; + flex-direction: column; + overflow: hidden; + @media print { + font-size: 11px; + height: auto; + width: inherit; + margin: 0 !important; + padding: 0.5cm !important; + overflow: hidden; + page-break-after: always; + } +` +// somehow scrollbars were not shown without explicitly setting height +const ScrollContainer = styled.div` + display: flex; + flex-direction: column; + flex-grow: 1; + overflow: hidden; + overflow-y: auto; + scrollbar-width: thin; +` + +export const Component = memo( + observer(({ id: idPassed }) => { + const params = useParams() + + const { pathname } = useLocation() + const client = useApolloClient() + const store = useContext(MobxContext) + const { enqueNotification, isPrint, user } = store + + const id = idPassed ?? params.tpopkontrId + const { data, loading, error, refetch } = useQuery(query, { + variables: { + id, + }, + }) + // DO NOT use apId from url because this form is also used for mass prints + const apId = + data?.tpopkontrById?.tpopByTpopId?.popByPopId?.apId ?? + '99999999-9999-9999-9999-999999999999' + + const zaehls = + data?.tpopkontrById?.tpopkontrzaehlsByTpopkontrId?.nodes ?? [] + + const row = data?.tpopkontrById ?? {} + + useEffect(() => { + let isActive = true + if (!loading) { + // loading data just finished + // check if tpopkontr exist + const tpopkontrCount = zaehls.length + if (tpopkontrCount === 0) { + // add counts for all ekzaehleinheit + // BUT DANGER: only for ekzaehleinheit with zaehleinheit_id + const ekzaehleinheits = ( + data?.tpopkontrById?.tpopByTpopId?.popByPopId?.apByApId + ?.ekzaehleinheitsByApId?.nodes ?? [] + ) + // remove ekzaehleinheits without zaehleinheit_id + .filter( + (z) => !!z?.tpopkontrzaehlEinheitWerteByZaehleinheitId?.code, + ) + + Promise.all( + ekzaehleinheits.map((z) => + client.mutate({ + mutation: createTpopkontrzaehl, + variables: { + tpopkontrId: row.id, + einheit: + z?.tpopkontrzaehlEinheitWerteByZaehleinheitId?.code ?? null, + changedBy: user.name, + }, + }), + ), + ) + .then(() => { + if (!isActive) return + + refetch() + }) + .catch((error) => { + if (!isActive) return + + enqueNotification({ + message: error.message, + options: { + variant: 'error', + }, + }) + }) + } + } + return () => { + isActive = false + } + }, [ + client, + data, + enqueNotification, + loading, + refetch, + row.id, + user.name, + zaehls.length, + ]) + + if (loading) return + + if (error) return + + if (Object.keys(row).length === 0) return null + + // console.log('Tpopfreiwkontr, isPrint:', isPrint) + + return ( + + {!pathname.includes('EKF') && ( + <> + + + )} + {isPrint ? +
+ : + + + } + + ) + }), +) diff --git a/src/components/Projekte/Daten/Tpopfreiwkontr/index.jsx b/src/components/Projekte/Daten/Tpopfreiwkontr/index.jsx index 6b90854551..99ebd32938 100644 --- a/src/components/Projekte/Daten/Tpopfreiwkontr/index.jsx +++ b/src/components/Projekte/Daten/Tpopfreiwkontr/index.jsx @@ -1,165 +1,14 @@ -import { memo, useEffect, useContext } from 'react' -import styled from '@emotion/styled' -import { observer } from 'mobx-react-lite' -import { useApolloClient, useQuery } from '@apollo/client' -import { useLocation, useParams } from 'react-router' +import { memo } from 'react' +import { useAtom } from 'jotai' -import { query } from './query.js' -import { createTpopkontrzaehl } from './createTpopkontrzaehl.js' -import { FormTitle } from '../../../shared/FormTitle/index.jsx' -import { StoreContext } from '../../../../storeContext.js' -import { Error } from '../../../shared/Error.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Form } from './Form/index.jsx' -import { Menu } from './Menu.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Tpopfreiwkontr } from './Tpopfreiwkontr.jsx' +import { List } from './List.jsx' -const Container = styled.div` - flex-grow: 0; - display: flex; - flex-direction: column; - overflow: hidden; - @media print { - font-size: 11px; - height: auto; - width: inherit; - margin: 0 !important; - padding: 0.5cm !important; - overflow: hidden; - page-break-after: always; - } -` -// somehow scrollbars were not shown without explicitly setting height -const ScrollContainer = styled.div` - display: flex; - flex-direction: column; - flex-grow: 1; - overflow: hidden; - overflow-y: auto; - scrollbar-width: thin; -` +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) -export const Component = memo( - observer(({ id: idPassed }) => { - const { tpopkontrId: idPassedFromUrl } = useParams() + if (isDesktopView) return - const { pathname } = useLocation() - const client = useApolloClient() - const store = useContext(StoreContext) - const { enqueNotification, isPrint, user } = store - - const id = idPassed ?? idPassedFromUrl - const { data, loading, error, refetch } = useQuery(query, { - variables: { - id, - }, - }) - // DO NOT use apId from url because this form is also used for mass prints - const apId = - data?.tpopkontrById?.tpopByTpopId?.popByPopId?.apId ?? - '99999999-9999-9999-9999-999999999999' - - const zaehls = - data?.tpopkontrById?.tpopkontrzaehlsByTpopkontrId?.nodes ?? [] - - const row = data?.tpopkontrById ?? {} - - useEffect(() => { - let isActive = true - if (!loading) { - // loading data just finished - // check if tpopkontr exist - const tpopkontrCount = zaehls.length - if (tpopkontrCount === 0) { - // add counts for all ekzaehleinheit - // BUT DANGER: only for ekzaehleinheit with zaehleinheit_id - const ekzaehleinheits = ( - data?.tpopkontrById?.tpopByTpopId?.popByPopId?.apByApId - ?.ekzaehleinheitsByApId?.nodes ?? [] - ) - // remove ekzaehleinheits without zaehleinheit_id - .filter( - (z) => !!z?.tpopkontrzaehlEinheitWerteByZaehleinheitId?.code, - ) - - Promise.all( - ekzaehleinheits.map((z) => - client.mutate({ - mutation: createTpopkontrzaehl, - variables: { - tpopkontrId: row.id, - einheit: - z?.tpopkontrzaehlEinheitWerteByZaehleinheitId?.code ?? null, - changedBy: user.name, - }, - }), - ), - ) - .then(() => { - if (!isActive) return - - refetch() - }) - .catch((error) => { - if (!isActive) return - - enqueNotification({ - message: error.message, - options: { - variant: 'error', - }, - }) - }) - } - } - return () => { - isActive = false - } - }, [ - client, - data, - enqueNotification, - loading, - refetch, - row.id, - user.name, - zaehls.length, - ]) - - if (loading) return - - if (error) return - - if (Object.keys(row).length === 0) return null - - // console.log('Tpopfreiwkontr, isPrint:', isPrint) - - return ( - - {!pathname.includes('EKF') && ( - <> - } - /> - - )} - {isPrint ? - - : - - - } - - ) - }), -) + return +}) diff --git a/src/components/Projekte/Daten/TpopfreiwkontrFilter/Form/Headdata/index.jsx b/src/components/Projekte/Daten/TpopfreiwkontrFilter/Form/Headdata/index.jsx index 6c98d71394..97f43abed2 100644 --- a/src/components/Projekte/Daten/TpopfreiwkontrFilter/Form/Headdata/index.jsx +++ b/src/components/Projekte/Daten/TpopfreiwkontrFilter/Form/Headdata/index.jsx @@ -4,7 +4,7 @@ import { observer } from 'mobx-react-lite' import { useQuery } from '@apollo/client' import { Select } from '../../../../../shared/Select.jsx' -import { StoreContext } from '../../../../../../storeContext.js' +import { MobxContext } from '../../../../../../mobxContext.js' import { query } from './query.js' import { Error } from '../../../../../shared/Error.jsx' @@ -46,7 +46,7 @@ const BearbVal = styled.div` export const Headdata = memo( observer(({ row, activeTab }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { dataFilterSetValue } = store.tree const { data, loading, error } = useQuery(query) diff --git a/src/components/Projekte/Daten/TpopfreiwkontrFilter/Form/index.jsx b/src/components/Projekte/Daten/TpopfreiwkontrFilter/Form/index.jsx index 10ad3eb269..102652ffdd 100644 --- a/src/components/Projekte/Daten/TpopfreiwkontrFilter/Form/index.jsx +++ b/src/components/Projekte/Daten/TpopfreiwkontrFilter/Form/index.jsx @@ -11,7 +11,7 @@ import { Danger } from './Danger.jsx' import { Remarks } from './Remarks.jsx' import { EkfRemarks } from './EkfRemarks.jsx' import { Verification } from './Verification.jsx' -import { StoreContext } from '../../../../../storeContext.js' +import { MobxContext } from '../../../../../mobxContext.js' import { ifIsNumericAsNumber } from '../../../../../modules/ifIsNumericAsNumber.js' const FormContainer = styled.div` @@ -98,7 +98,7 @@ const GridContainer = styled.div` export const Form = memo( observer(({ row, activeTab }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { dataFilterSetValue } = store.tree const saveToDb = useCallback( diff --git a/src/components/Projekte/Daten/TpopfreiwkontrFilter/Tabs.jsx b/src/components/Projekte/Daten/TpopfreiwkontrFilter/Tabs.jsx index f1ac681e1c..e0b1d847a4 100644 --- a/src/components/Projekte/Daten/TpopfreiwkontrFilter/Tabs.jsx +++ b/src/components/Projekte/Daten/TpopfreiwkontrFilter/Tabs.jsx @@ -4,7 +4,7 @@ import Tab from '@mui/material/Tab' import styled from '@emotion/styled' import { initial as tpopfreiwkontr } from '../../../../store/Tree/DataFilter/tpopfreiwkontr.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' const Row = styled.div`` const Title = styled.div` @@ -24,7 +24,7 @@ const StyledTab = styled(Tab)` ` export const Tabs = ({ activeTab, setActiveTab, dataFilter }) => { - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { dataFilterAddOr } = store.tree const lastFilterIsEmpty = diff --git a/src/components/Projekte/Daten/TpopfreiwkontrFilter/index.jsx b/src/components/Projekte/Daten/TpopfreiwkontrFilter/index.jsx index 6e7144f354..e7be394372 100644 --- a/src/components/Projekte/Daten/TpopfreiwkontrFilter/index.jsx +++ b/src/components/Projekte/Daten/TpopfreiwkontrFilter/index.jsx @@ -6,7 +6,7 @@ import { useParams } from 'react-router' import { query } from './query.js' import { FilterTitle } from '../../../shared/FilterTitle.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { Form } from './Form/index.jsx' import { Tabs } from './Tabs.jsx' @@ -52,7 +52,7 @@ export const TpopfreiwkontrFilter = memo( observer(() => { const { apId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const tree = store.tree const { dataFilter, diff --git a/src/components/Projekte/Daten/Tpopfreiwkontrs/List.jsx b/src/components/Projekte/Daten/Tpopfreiwkontrs/List.jsx new file mode 100644 index 0000000000..8f66fe1701 --- /dev/null +++ b/src/components/Projekte/Daten/Tpopfreiwkontrs/List.jsx @@ -0,0 +1,30 @@ +import { memo, useContext } from 'react' +import { observer } from 'mobx-react-lite' + +import { MobxContext } from '../../../../mobxContext.js' +import { useTpopfreiwkontrsNavData } from '../../../../modules/useTpopfreiwkontrsNavData.js' +import { List as SharedList } from '../../../shared/List/index.jsx' +import { Menu } from './Menu.jsx' +import { Spinner } from '../../../shared/Spinner.jsx' +import { Error } from '../../../shared/Error.jsx' + +export const List = memo( + observer(() => { + const store = useContext(MobxContext) + const { nodeLabelFilter } = store.tree + + const { navData, isLoading, error } = useTpopfreiwkontrsNavData() + + if (isLoading) return + + if (error) return + + return ( + + ) + }), +) diff --git a/src/components/Projekte/Daten/Tpopfreiwkontrs/Menu.jsx b/src/components/Projekte/Daten/Tpopfreiwkontrs/Menu.jsx index 1e8d15d02b..137b4e052e 100644 --- a/src/components/Projekte/Daten/Tpopfreiwkontrs/Menu.jsx +++ b/src/components/Projekte/Daten/Tpopfreiwkontrs/Menu.jsx @@ -13,14 +13,14 @@ import { observer } from 'mobx-react-lite' import { useAtom } from 'jotai' import { MenuBar, buttonWidth } from '../../../shared/MenuBar/index.jsx' +import { FilterButton } from '../../../shared/MenuBar/FilterButton.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { openLowerNodes } from '../../TreeContainer/openLowerNodes/index.js' import { closeLowerNodes } from '../../TreeContainer/closeLowerNodes.js' import { moveTo } from '../../../../modules/moveTo/index.js' import { copyTo } from '../../../../modules/copyTo/index.js' -import { StoreContext } from '../../../../storeContext.js' -import { LabelFilter, labelFilterWidth } from '../../../shared/LabelFilter.jsx' -import { listLabelFilterIsIconAtom } from '../../../../JotaiStore/index.js' +import { MobxContext } from '../../../../mobxContext.js' +import { hideTreeAtom } from '../../../../JotaiStore/index.js' const MoveIcon = styled(MdOutlineMoveDown)` color: white; @@ -31,13 +31,13 @@ const CopyIcon = styled(MdContentCopy)` const iconStyle = { color: 'white' } export const Menu = memo( - observer(() => { + observer(({ toggleFilterInput }) => { const { search } = useLocation() const navigate = useNavigate() const apolloClient = useApolloClient() const tanstackQueryClient = useQueryClient() const { projId, apId, popId, tpopId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { setMoving, moving, setCopying, copying } = store const onClickAdd = useCallback(async () => { @@ -77,7 +77,7 @@ export const Menu = memo( queryKey: [`treeTpopfreiwkontr`], }) tanstackQueryClient.invalidateQueries({ - queryKey: [`treeTpopFolders`], + queryKey: [`treeTpop`], }) const id = result?.data?.createTpopkontr?.tpopkontr?.id navigate(`./${id}${search}`) @@ -152,31 +152,35 @@ export const Menu = memo( }) }, [setCopying]) - const [labelFilterIsIcon] = useAtom(listLabelFilterIsIconAtom) + const [hideTree] = useAtom(hideTreeAtom) return ( - + {!!toggleFilterInput && ( + + )} - - - - - - - - - - + {!hideTree && ( + + + + + + )} + {!hideTree && ( + + + + + + )} {isMovingEkf && ( diff --git a/src/components/Projekte/Daten/Tpopfreiwkontrs/index.jsx b/src/components/Projekte/Daten/Tpopfreiwkontrs/index.jsx index 70b02d93d0..cbbc3bcaf2 100644 --- a/src/components/Projekte/Daten/Tpopfreiwkontrs/index.jsx +++ b/src/components/Projekte/Daten/Tpopfreiwkontrs/index.jsx @@ -1,47 +1,14 @@ -import { memo, useContext } from 'react' -import { useApolloClient, gql } from '@apollo/client' -import { useQuery } from '@tanstack/react-query' -import { observer } from 'mobx-react-lite' -import { useParams } from 'react-router' +import { memo } from 'react' +import { useAtom } from 'jotai' -import { StoreContext } from '../../../../storeContext.js' -import { createApsQuery } from '../../../../modules/createApsQuery.js' -import { createTpopfreiwkontrQuery } from '../../../../modules/createTpopfreiwkontrQuery.js' -import { List } from '../../../shared/List/index.jsx' -import { Menu } from './Menu.jsx' -import { Spinner } from '../../../shared/Spinner.jsx' -import { Error } from '../../../shared/Error.jsx' +import { isDesktopViewAtom } from '../../../../JotaiStore/index.js' +import { Component as Tpop } from '../Tpop/Tpop.jsx' +import { List } from './List.jsx' -export const Component = memo( - observer(() => { - const { tpopId } = useParams() - const apolloClient = useApolloClient() - const store = useContext(StoreContext) - const { ekfGqlFilterForTree, nodeLabelFilter } = store.tree +export const Component = memo(() => { + const [isDesktopView] = useAtom(isDesktopViewAtom) - const { data, isLoading, error } = useQuery( - createTpopfreiwkontrQuery({ - tpopId, - ekfGqlFilterForTree, - apolloClient, - }), - ) - const tpopfreiwkontrs = data?.data?.tpopById?.tpopfreiwkontrs?.nodes ?? [] - const totalCount = - data?.data?.tpopById?.tpopfreiwkontrsCount?.totalCount ?? 0 + if (isDesktopView) return - if (isLoading) return - - if (error) return - - return ( - } - highlightSearchString={nodeLabelFilter.tpopkontr} - /> - ) - }), -) + return +}) diff --git a/src/components/Projekte/Daten/Tpopkontrzaehl/Menu.jsx b/src/components/Projekte/Daten/Tpopkontrzaehl/Menu.jsx index 418d613d92..b3cffe95e2 100644 --- a/src/components/Projekte/Daten/Tpopkontrzaehl/Menu.jsx +++ b/src/components/Projekte/Daten/Tpopkontrzaehl/Menu.jsx @@ -14,7 +14,7 @@ import styled from '@emotion/styled' import { MenuBar } from '../../../shared/MenuBar/index.jsx' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { MenuTitle } from '../../../shared/Files/Menu/index.jsx' const iconStyle = { color: 'white' } @@ -27,7 +27,7 @@ export const Menu = memo( const tanstackQueryClient = useQueryClient() const { projId, apId, popId, tpopId, tpopkontrId, tpopkontrzaehlId } = useParams() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const onClickAdd = useCallback(async () => { let result @@ -63,6 +63,9 @@ export const Menu = memo( tanstackQueryClient.invalidateQueries({ queryKey: [`treeTpopfeldkontrzaehlFolders`], }) + tanstackQueryClient.invalidateQueries({ + queryKey: [`treeTpopfeldkontr`], + }) const id = result?.data?.createTpopkontrzaehl?.tpopkontrzaehl?.id navigate( `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Teil-Populationen/${tpopId}/Feld-Kontrollen/${tpopkontrId}/Zaehlungen/${id}${search}`, @@ -120,6 +123,9 @@ export const Menu = memo( tanstackQueryClient.invalidateQueries({ queryKey: [`treeTpopfeldkontrzaehlFolders`], }) + tanstackQueryClient.invalidateQueries({ + queryKey: [`treeTpopfeldkontr`], + }) // navigate to parent navigate( `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Teil-Populationen/${tpopId}/Feld-Kontrollen/${tpopkontrId}/Zaehlungen${search}`, diff --git a/src/components/Projekte/Daten/Tpopkontrzaehl/index.jsx b/src/components/Projekte/Daten/Tpopkontrzaehl/index.jsx index ff2f5b8e93..a5525bb603 100644 --- a/src/components/Projekte/Daten/Tpopkontrzaehl/index.jsx +++ b/src/components/Projekte/Daten/Tpopkontrzaehl/index.jsx @@ -10,7 +10,7 @@ import { TextField } from '../../../shared/TextField.jsx' import { Select } from '../../../shared/Select.jsx' import { FormTitle } from '../../../shared/FormTitle/index.jsx' import { query } from './query.js' -import { StoreContext } from '../../../../storeContext.js' +import { MobxContext } from '../../../../mobxContext.js' import { ifIsNumericAsNumber } from '../../../../modules/ifIsNumericAsNumber.js' import { ErrorBoundary } from '../../../shared/ErrorBoundary.jsx' import { Error } from '../../../shared/Error.jsx' @@ -46,7 +46,7 @@ export const Component = memo( const client = useApolloClient() const queryClient = useQueryClient() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const [fieldErrors, setFieldErrors] = useState({}) @@ -130,7 +130,7 @@ export const Component = memo( } + MenuBarComponent={Menu} /> + + + + + + + + + + + {isAnpflanzung && ( + <> + - - - - - - - - - - - {isAnpflanzung && ( - <> - - - - - - - - - - - - - - - } - - ) - }), -) diff --git a/src/components/shared/List/index.jsx b/src/components/shared/List/index.jsx index 0cd960a62f..c39d1bc2d3 100644 --- a/src/components/shared/List/index.jsx +++ b/src/components/shared/List/index.jsx @@ -5,6 +5,7 @@ import Highlighter from 'react-highlight-words' import { FormTitle } from '../FormTitle/index.jsx' import { ErrorBoundary } from '../ErrorBoundary.jsx' +import { navData } from '../../Bookmarks/NavTo/Navs/Projects.jsx' const Container = styled.div` display: flex; @@ -38,7 +39,12 @@ const Row = styled.div` ` export const List = memo( - ({ items, title, totalCount, menuBar = null, highlightSearchString }) => { + ({ + navData, + MenuBarComponent = null, + menuBarProps = {}, + highlightSearchString, + }) => { const navigate = useNavigate() const { search } = useLocation() @@ -51,11 +57,13 @@ export const List = memo( - {items.map((item) => { + {navData.menus.map((item) => { const label = item.label ?? item.labelEkf ?? item.labelEk return ( @@ -63,12 +71,20 @@ export const List = memo( key={item.id} onClick={onClickRow.bind(this, item)} > + {!!item.labelLeftElements?.length && + item.labelLeftElements.map((El, index) => ( + + ))} {highlightSearchString ? : label} + {!!item.labelRightElements?.length && + item.labelRightElements.map((El, index) => ( + + ))} ) })} diff --git a/src/components/shared/MenuBar/FilterButton.jsx b/src/components/shared/MenuBar/FilterButton.jsx new file mode 100644 index 0000000000..d7cd48512b --- /dev/null +++ b/src/components/shared/MenuBar/FilterButton.jsx @@ -0,0 +1,26 @@ +import { memo, useCallback } from 'react' +import IconButton from '@mui/material/IconButton' +import Tooltip from '@mui/material/Tooltip' +import { MdFilterAlt } from 'react-icons/md' +import styled from '@emotion/styled' + +const StyledButton = styled(IconButton)` + color: white; +` + +export const FilterButton = memo(({ toggleFilterInput }) => { + const onClick = useCallback(() => { + toggleFilterInput() + }, [toggleFilterInput]) + + return ( + + + + + + ) +}) diff --git a/src/components/shared/MenuBar/index.jsx b/src/components/shared/MenuBar/index.jsx index b6ea58abc0..4651c1ece9 100644 --- a/src/components/shared/MenuBar/index.jsx +++ b/src/components/shared/MenuBar/index.jsx @@ -74,7 +74,7 @@ const StyledMenu = styled(Menu)` ` const MenuContent = styled.div` display: flex; - flex-wrap: wrap; + flex-direction: column; row-gap: 3px; background-color: ${(props) => props.bgColor}; overflow: hidden; @@ -265,7 +265,12 @@ export const MenuBar = memo( open={menuIsOpen} onClose={onCloseMenu} > - {menus} + + {menus} + )} diff --git a/src/components/shared/Notifier.jsx b/src/components/shared/Notifier.jsx index 7a2da268bd..bd0b8db909 100644 --- a/src/components/shared/Notifier.jsx +++ b/src/components/shared/Notifier.jsx @@ -2,12 +2,12 @@ import { useEffect, useContext, useState, memo } from 'react' import { useSnackbar } from 'notistack' import { observer } from 'mobx-react-lite' -import { StoreContext } from '../../storeContext.js' +import { MobxContext } from '../../mobxContext.js' export const Notifier = memo( observer(() => { const { enqueueSnackbar } = useSnackbar() - const store = useContext(StoreContext) + const store = useContext(MobxContext) const { notifications, removeNotification } = store const [displayed, setDisplayed] = useState([]) diff --git a/src/components/shared/Uploader/index.jsx b/src/components/shared/Uploader/index.jsx index 9a95c3bea4..4a619d3666 100644 --- a/src/components/shared/Uploader/index.jsx +++ b/src/components/shared/Uploader/index.jsx @@ -1,4 +1,4 @@ -import { forwardRef, memo, useContext } from 'react' +import { memo, useContext } from 'react' import { FileUploaderRegular, defineLocale } from '@uploadcare/react-uploader' import '@uploadcare/react-uploader/core.css' diff --git a/src/idbContext.js b/src/idbContext.js index e6e7f1e1d9..9df10e643b 100644 --- a/src/idbContext.js +++ b/src/idbContext.js @@ -1,5 +1,3 @@ import { createContext } from 'react' export const IdbContext = createContext({}) -export const Provider = IdbContext.Provider -export const Consumer = IdbContext.Consumer diff --git a/src/main.jsx b/src/main.jsx index 69c0767379..4bc5660694 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -6,11 +6,10 @@ import { App } from './App.jsx' // https://vite-pwa-org.netlify.app/guide/auto-update.html registerSW({ immediate: true }) -createRoot(document.getElementById('root')) - // todo: causes mstPersist to run twice - // .render( - // - // - // , - // ) - .render() +createRoot(document.getElementById('root')).render() +// todo: causes mstPersist to run twice +// .render( +// +// +// , +// ) diff --git a/src/mobxContext.js b/src/mobxContext.js new file mode 100644 index 0000000000..9ccabb70a1 --- /dev/null +++ b/src/mobxContext.js @@ -0,0 +1,3 @@ +import { createContext } from 'react' + +export const MobxContext = createContext({}) diff --git a/src/modules/constants.js b/src/modules/constants.js index 6f770b8964..3d7356bbf9 100644 --- a/src/modules/constants.js +++ b/src/modules/constants.js @@ -6,4 +6,6 @@ export const constants = { '0aba83f3-d515-4806-bc14-838f8f6d0fe6', '96efab5c-8bfc-11e7-a848-2b4184d2c8eb', ], + mobileViewMaxWidth: 999, + minWidthToShowTitle: 1040, } diff --git a/src/modules/copyTo/index.js b/src/modules/copyTo/index.js index fce0dc9a66..ebf9355e95 100644 --- a/src/modules/copyTo/index.js +++ b/src/modules/copyTo/index.js @@ -253,6 +253,9 @@ export const copyTo = async ({ tanstackQueryClient.invalidateQueries({ queryKey: ['treeApFolders'], }) + tanstackQueryClient.invalidateQueries({ + queryKey: ['treeAp'], + }) } if (table === 'tpop') { tanstackQueryClient.invalidateQueries({ @@ -261,13 +264,16 @@ export const copyTo = async ({ tanstackQueryClient.invalidateQueries({ queryKey: ['treePopFolders'], }) + tanstackQueryClient.invalidateQueries({ + queryKey: ['treePop'], + }) } if (table === 'tpopmassn') { tanstackQueryClient.invalidateQueries({ queryKey: ['treeTpopmassn'], }) tanstackQueryClient.invalidateQueries({ - queryKey: ['treeTpopFolders'], + queryKey: ['treeTpop'], }) } if (table === 'tpopfeldkontr') { @@ -283,7 +289,7 @@ export const copyTo = async ({ queryKey: ['treeTpopfeldkontr'], }) tanstackQueryClient.invalidateQueries({ - queryKey: ['treeTpopFolders'], + queryKey: ['treeTpop'], }) } if (table === 'tpopfreiwkontr') { @@ -291,7 +297,7 @@ export const copyTo = async ({ queryKey: ['treeTpopfreiwkontr'], }) tanstackQueryClient.invalidateQueries({ - queryKey: ['treeTpopFolders'], + queryKey: ['treeTpop'], }) } diff --git a/src/modules/createAdressesQuery.js b/src/modules/createAdressesQuery.js deleted file mode 100644 index 86c8642d51..0000000000 --- a/src/modules/createAdressesQuery.js +++ /dev/null @@ -1,23 +0,0 @@ -import { gql } from '@apollo/client' - -export const createAdressesQuery = ({ adresseGqlFilterForTree, apolloClient }) => ({ - queryKey: ['treeAdresse', adresseGqlFilterForTree], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeAdressesQuery($adressesFilter: AdresseFilter!) { - allAdresses(filter: $adressesFilter, orderBy: LABEL_ASC) { - nodes { - id - label - } - } - totalCount: allAdresses { - totalCount - } - } - `, - variables: { adressesFilter: adresseGqlFilterForTree }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createApartsQuery.js b/src/modules/createApartsQuery.js deleted file mode 100644 index 356271835f..0000000000 --- a/src/modules/createApartsQuery.js +++ /dev/null @@ -1,36 +0,0 @@ -import { gql } from '@apollo/client' - -export const createApartsQuery = ({ - apId, - apartGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: ['treeApart', apId, apartGqlFilterForTree], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeApartsQuery( - $apId: UUID! - $apartsFilter: ApartFilter! - ) { - apById(id: $apId) { - id - apartsByApId(filter: $apartsFilter, orderBy: LABEL_ASC) { - nodes { - id - label - } - } - apartsCount: apartsByApId { - totalCount - } - } - } - `, - variables: { - apartsFilter: apartGqlFilterForTree, - apId, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createApbersQuery.js b/src/modules/createApbersQuery.js deleted file mode 100644 index 7834aae47b..0000000000 --- a/src/modules/createApbersQuery.js +++ /dev/null @@ -1,36 +0,0 @@ -import { gql } from '@apollo/client' - -export const createApbersQuery = ({ - apId, - apberGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: ['treeApber', apId, apberGqlFilterForTree], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeApbersQuery( - $apId: UUID! - $apbersFilter: ApberFilter! - ) { - apById(id: $apId) { - id - apbersByApId(filter: $apbersFilter, orderBy: LABEL_ASC) { - nodes { - id - label - } - } - apbersCount: apbersByApId { - totalCount - } - } - } - `, - variables: { - apbersFilter: apberGqlFilterForTree, - apId, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createApberuebersichtsQuery.js b/src/modules/createApberuebersichtsQuery.js deleted file mode 100644 index ff8ea3cb4c..0000000000 --- a/src/modules/createApberuebersichtsQuery.js +++ /dev/null @@ -1,31 +0,0 @@ -import { gql } from '@apollo/client' - -export const createApberuebersichtsQuery = ({ - projId, - apberuebersichtGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: ['treeApberuebersicht', projId, apberuebersichtGqlFilterForTree], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeApberuebersichtsQuery($apberuebersichtsFilter: ApberuebersichtFilter!, $projId: UUID!) { - allApberuebersichts(filter: $apberuebersichtsFilter, orderBy: LABEL_ASC) { - nodes { - id - projId - label - } - } - totalCount: allApberuebersichts(filter: { projId: { equalTo: $projId } }) { - totalCount - } - } - `, - variables: { - apberuebersichtsFilter: apberuebersichtGqlFilterForTree, - projId, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createApsQuery.js b/src/modules/createApsQuery.js deleted file mode 100644 index 976823349f..0000000000 --- a/src/modules/createApsQuery.js +++ /dev/null @@ -1,30 +0,0 @@ -import { gql } from '@apollo/client' - -export const createApsQuery = ({ - projId, - apGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: ['treeAp', projId, apGqlFilterForTree], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeApsQuery($apsFilter: ApFilter!, $projId: UUID!) { - allAps(filter: $apsFilter, orderBy: LABEL_ASC) { - nodes { - id - label - } - } - totalCount: allAps(filter: { projId: { equalTo: $projId } }) { - totalCount - } - } - `, - variables: { - apsFilter: apGqlFilterForTree, - projId, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createAssozartsQuery.js b/src/modules/createAssozartsQuery.js deleted file mode 100644 index 8c0aa4d2a6..0000000000 --- a/src/modules/createAssozartsQuery.js +++ /dev/null @@ -1,36 +0,0 @@ -import { gql } from '@apollo/client' - -export const createAssozartsQuery = ({ - apId, - assozartGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: ['treeAssozart', apId, assozartGqlFilterForTree], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeAssozartsQuery( - $apId: UUID! - $assozartsFilter: AssozartFilter! - ) { - apById(id: $apId) { - id - assozartsByApId(filter: $assozartsFilter, orderBy: LABEL_ASC) { - nodes { - id - label - } - } - assozartsCount: assozartsByApId { - totalCount - } - } - } - `, - variables: { - assozartsFilter: assozartGqlFilterForTree, - apId, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createBeobsQuery.js b/src/modules/createBeobsQuery.js deleted file mode 100644 index 359b08fad2..0000000000 --- a/src/modules/createBeobsQuery.js +++ /dev/null @@ -1,59 +0,0 @@ -import { gql } from '@apollo/client' - -export const createBeobsQuery = ({ - tpopId, - apId, - beobGqlFilterForTree, - apolloClient, - type, -}) => { - const gqlFilter = beobGqlFilterForTree(type) - if (tpopId) { - gqlFilter.tpopId = { equalTo: tpopId } - } - if (apId) { - gqlFilter.aeTaxonomyByArtId = { - apartsByArtId: { - some: { - apId: { equalTo: apId }, - }, - }, - } - } - - const totalCountFilter = { ...gqlFilter } - delete totalCountFilter.label - delete totalCountFilter.geomPoint - - const key0 = - type === 'zugeordnet' ? 'treeBeobZugeordnet' - : type === 'nichtBeurteilt' ? 'treeBeobNichtBeurteilt' - : type === 'nichtZuzuordnen' ? 'treeBeobNichtZuzuordnen' - : 'treeNoType' - - return { - queryKey: [key0, tpopId, gqlFilter], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeBeobsQuery( - $gqlFilter: BeobFilter! - $totalCountFilter: BeobFilter! - ) { - allBeobs(filter: $gqlFilter, orderBy: [DATUM_DESC, AUTOR_ASC]) { - nodes { - id - label - } - } - beobsCount: allBeobs(filter: $totalCountFilter) { - totalCount - } - } - `, - variables: { gqlFilter, totalCountFilter }, - // tanstack is caching - fetchPolicy: 'no-cache', - }), - } -} diff --git a/src/modules/createCurrentissuesQuery.js b/src/modules/createCurrentissuesQuery.js deleted file mode 100644 index ef149a5a36..0000000000 --- a/src/modules/createCurrentissuesQuery.js +++ /dev/null @@ -1,22 +0,0 @@ -import { gql } from '@apollo/client' - -export const createCurrentissuesQuery = ({ apolloClient }) => ({ - queryKey: ['treeCurrentissues'], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeCurrentissuesQuery { - allCurrentissues(orderBy: [SORT_ASC, TITLE_ASC]) { - nodes { - id - label - } - } - totalCount: allCurrentissues { - totalCount - } - } - `, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createEkAbrechnungstypWertesQuery.js b/src/modules/createEkAbrechnungstypWertesQuery.js deleted file mode 100644 index edebca6c6a..0000000000 --- a/src/modules/createEkAbrechnungstypWertesQuery.js +++ /dev/null @@ -1,36 +0,0 @@ -import { gql } from '@apollo/client' - -export const createEkAbrechnungstypWertesQuery = ({ - ekAbrechnungstypWerteGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: [ - 'treeEkAbrechnungstypWerte', - ekAbrechnungstypWerteGqlFilterForTree, - ], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeEkAbrechnungstypWertesQuery( - $filter: EkAbrechnungstypWerteFilter! - ) { - allEkAbrechnungstypWertes( - filter: $filter - orderBy: [SORT_ASC, TEXT_ASC] - ) { - nodes { - id - label - } - } - totalCount: allEkAbrechnungstypWertes { - totalCount - } - } - `, - variables: { - filter: ekAbrechnungstypWerteGqlFilterForTree, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createEkfrequenzsQuery.js b/src/modules/createEkfrequenzsQuery.js deleted file mode 100644 index c72081b2ce..0000000000 --- a/src/modules/createEkfrequenzsQuery.js +++ /dev/null @@ -1,37 +0,0 @@ -import { gql } from '@apollo/client' - -export const createEkfrequenzsQuery = ({ - apId, - ekfrequenzGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: ['treeEkfrequenz', apId, ekfrequenzGqlFilterForTree], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeEkfrequenzsQuery( - $apId: UUID! - $ekfrequenzsFilter: EkfrequenzFilter! - ) { - apById(id: $apId) { - id - ekfrequenzsByApId(filter: $ekfrequenzsFilter, orderBy: SORT_ASC) { - nodes { - id - code - label: code - } - } - ekfrequenzsCount: ekfrequenzsByApId { - totalCount - } - } - } - `, - variables: { - ekfrequenzsFilter: ekfrequenzGqlFilterForTree, - apId, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createEkzaehleinheitsQuery.js b/src/modules/createEkzaehleinheitsQuery.js deleted file mode 100644 index 6b40b2f767..0000000000 --- a/src/modules/createEkzaehleinheitsQuery.js +++ /dev/null @@ -1,39 +0,0 @@ -import { gql } from '@apollo/client' - -export const createEkzaehleinheitsQuery = ({ - apId, - ekzaehleinheitGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: ['treeEkzaehleinheit', apId, ekzaehleinheitGqlFilterForTree], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeEkzaehleinheitsQuery( - $apId: UUID! - $ekzaehleinheitsFilter: EkzaehleinheitFilter! - ) { - apById(id: $apId) { - id - ekzaehleinheitsByApId( - filter: $ekzaehleinheitsFilter - orderBy: [SORT_ASC, LABEL_ASC] - ) { - nodes { - id - label - } - } - ekzaehleinheitsCount: ekzaehleinheitsByApId { - totalCount - } - } - } - `, - variables: { - ekzaehleinheitsFilter: ekzaehleinheitGqlFilterForTree, - apId, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createErfkritsQuery.js b/src/modules/createErfkritsQuery.js deleted file mode 100644 index b5b55b2110..0000000000 --- a/src/modules/createErfkritsQuery.js +++ /dev/null @@ -1,36 +0,0 @@ -import { gql } from '@apollo/client' - -export const createErfkritsQuery = ({ - apId, - erfkritGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: ['treeErfkrit', apId, erfkritGqlFilterForTree], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeErfkritsQuery($apId: UUID!, $erfkritsFilter: ErfkritFilter!) { - apById(id: $apId) { - id - erfkritsByApId( - filter: $erfkritsFilter - orderBy: AP_ERFKRIT_WERTE_BY_ERFOLG__SORT_ASC - ) { - nodes { - id - label - } - } - erfkritsCount: erfkritsByApId { - totalCount - } - } - } - `, - variables: { - erfkritsFilter: erfkritGqlFilterForTree, - apId, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createNewPopFromBeob/index.js b/src/modules/createNewPopFromBeob/index.js index 00de439e3c..3bf25a2dd5 100644 --- a/src/modules/createNewPopFromBeob/index.js +++ b/src/modules/createNewPopFromBeob/index.js @@ -183,6 +183,9 @@ export const createNewPopFromBeob = async ({ tanstackQueryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + tanstackQueryClient.invalidateQueries({ + queryKey: ['treeAp'], + }) tanstackQueryClient.invalidateQueries({ queryKey: [`treeBeobZugeordnet`], }) diff --git a/src/modules/createPopbersQuery.js b/src/modules/createPopbersQuery.js deleted file mode 100644 index d714ad574a..0000000000 --- a/src/modules/createPopbersQuery.js +++ /dev/null @@ -1,36 +0,0 @@ -import { gql } from '@apollo/client' - -export const createPopbersQuery = ({ - popId, - popberGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: ['treePopber', popId, popberGqlFilterForTree], - queryFn: () => - apolloClient.query({ - query: gql` - query TreePopbersQuery( - $popId: UUID! - $popbersFilter: PopberFilter! - ) { - popById(id: $popId) { - id - popbersByPopId(filter: $popbersFilter, orderBy: LABEL_ASC) { - nodes { - id - label - } - } - popbersCount: popbersByPopId { - totalCount - } - } - } - `, - variables: { - popbersFilter: popberGqlFilterForTree, - popId, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createPopmassnbersQuery.js b/src/modules/createPopmassnbersQuery.js deleted file mode 100644 index f2f8db1f47..0000000000 --- a/src/modules/createPopmassnbersQuery.js +++ /dev/null @@ -1,39 +0,0 @@ -import { gql } from '@apollo/client' - -export const createPopmassnbersQuery = ({ - popId, - popmassnberGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: ['treePopmassnber', popId, popmassnberGqlFilterForTree], - queryFn: () => - apolloClient.query({ - query: gql` - query TreePopmassnbersQuery( - $popId: UUID! - $popmassnbersFilter: PopmassnberFilter! - ) { - popById(id: $popId) { - id - popmassnbersByPopId( - filter: $popmassnbersFilter - orderBy: LABEL_ASC - ) { - nodes { - id - label - } - } - popmassnbersCount: popmassnbersByPopId { - totalCount - } - } - } - `, - variables: { - popmassnbersFilter: popmassnberGqlFilterForTree, - popId, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createPopsQuery.js b/src/modules/createPopsQuery.js deleted file mode 100644 index 1b511afef1..0000000000 --- a/src/modules/createPopsQuery.js +++ /dev/null @@ -1,35 +0,0 @@ -import { gql } from '@apollo/client' - -export const createPopsQuery = ({ - apId, - popGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: ['treePop', apId, popGqlFilterForTree], - queryFn: () => - apolloClient.query({ - query: gql` - query TreePopQuery($apId: UUID!, $popsFilter: PopFilter!) { - apById(id: $apId) { - id - popsByApId(filter: $popsFilter, orderBy: [NR_ASC, NAME_ASC]) { - totalCount - nodes { - id - label - status - } - } - popsCount: popsByApId { - totalCount - } - } - } - `, - variables: { - popsFilter: popGqlFilterForTree, - apId, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createTpopApberrelevantGrundWertesQuery.js b/src/modules/createTpopApberrelevantGrundWertesQuery.js deleted file mode 100644 index 367c7dd9c9..0000000000 --- a/src/modules/createTpopApberrelevantGrundWertesQuery.js +++ /dev/null @@ -1,36 +0,0 @@ -import { gql } from '@apollo/client' - -export const createTpopApberrelevantGrundWertesQuery = ({ - tpopApberrelevantGrundWerteGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: [ - 'treeTpopApberrelevantGrundWerte', - tpopApberrelevantGrundWerteGqlFilterForTree, - ], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeTpopApberrelevantGrundWertesQuery( - $filter: TpopApberrelevantGrundWerteFilter! - ) { - allTpopApberrelevantGrundWertes( - filter: $filter - orderBy: [SORT_ASC, TEXT_ASC] - ) { - nodes { - id - label - } - } - totalCount: allTpopApberrelevantGrundWertes { - totalCount - } - } - `, - variables: { - filter: tpopApberrelevantGrundWerteGqlFilterForTree, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createTpopbersQuery.js b/src/modules/createTpopbersQuery.js deleted file mode 100644 index 08bc56c7e0..0000000000 --- a/src/modules/createTpopbersQuery.js +++ /dev/null @@ -1,36 +0,0 @@ -import { gql } from '@apollo/client' - -export const createTpopbersQuery = ({ - tpopId, - tpopberGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: ['treeTpopber', tpopId, tpopberGqlFilterForTree], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeTpopbersQuery( - $tpopId: UUID! - $tpopbersFilter: TpopberFilter! - ) { - tpopById(id: $tpopId) { - id - tpopbersByTpopId(filter: $tpopbersFilter, orderBy: LABEL_ASC) { - nodes { - id - label - } - } - tpopbersCount: tpopbersByTpopId { - totalCount - } - } - } - `, - variables: { - tpopbersFilter: tpopberGqlFilterForTree, - tpopId, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createTpopfeldkontrQuery.js b/src/modules/createTpopfeldkontrQuery.js deleted file mode 100644 index d6d0ade264..0000000000 --- a/src/modules/createTpopfeldkontrQuery.js +++ /dev/null @@ -1,41 +0,0 @@ -import { gql } from '@apollo/client' - -export const createTpopfeldkontrQuery = ({ - tpopId, - ekGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: ['treeTpopfeldkontr', tpopId, ekGqlFilterForTree], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeTpopfeldkontrsQuery( - $tpopId: UUID! - $tpopkontrsFilter: TpopkontrFilter! - ) { - tpopById(id: $tpopId) { - id - tpopfeldkontrs: tpopkontrsByTpopId( - filter: $tpopkontrsFilter - orderBy: [JAHR_ASC, DATUM_ASC] - ) { - nodes { - id - labelEk - } - } - tpopfeldkontrsCount: tpopkontrsByTpopId( - filter: { typ: { distinctFrom: "Freiwilligen-Erfolgskontrolle" } } - ) { - totalCount - } - } - } - `, - variables: { - tpopkontrsFilter: ekGqlFilterForTree, - tpopId, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createTpopfreiwkontrQuery.js b/src/modules/createTpopfreiwkontrQuery.js deleted file mode 100644 index 3c0f2c1d5f..0000000000 --- a/src/modules/createTpopfreiwkontrQuery.js +++ /dev/null @@ -1,41 +0,0 @@ -import { gql } from '@apollo/client' - -export const createTpopfreiwkontrQuery = ({ - tpopId, - ekfGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: ['treeTpopfreiwkontr', tpopId, ekfGqlFilterForTree], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeTpopfreiwkontrsQuery( - $tpopId: UUID! - $tpopkontrsFilter: TpopkontrFilter! - ) { - tpopById(id: $tpopId) { - id - tpopfreiwkontrs: tpopkontrsByTpopId( - filter: $tpopkontrsFilter - orderBy: [JAHR_ASC, DATUM_ASC] - ) { - nodes { - id - labelEkf - } - } - tpopfreiwkontrsCount: tpopkontrsByTpopId( - filter: { typ: { equalTo: "Freiwilligen-Erfolgskontrolle" } } - ) { - totalCount - } - } - } - `, - variables: { - tpopkontrsFilter: ekfGqlFilterForTree, - tpopId, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createTpopkontrzaehlEinheitWertesQuery.js b/src/modules/createTpopkontrzaehlEinheitWertesQuery.js deleted file mode 100644 index 0bcd19a6da..0000000000 --- a/src/modules/createTpopkontrzaehlEinheitWertesQuery.js +++ /dev/null @@ -1,36 +0,0 @@ -import { gql } from '@apollo/client' - -export const createTpopkontrzaehlEinheitWertesQuery = ({ - tpopkontrzaehlEinheitWerteGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: [ - 'treeTpopkontrzaehlEinheitWerte', - tpopkontrzaehlEinheitWerteGqlFilterForTree, - ], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeTpopkontrzaehlEinheitWertesQuery( - $filter: TpopkontrzaehlEinheitWerteFilter! - ) { - allTpopkontrzaehlEinheitWertes( - filter: $filter - orderBy: [SORT_ASC, TEXT_ASC] - ) { - nodes { - id - label - } - } - totalCount: allTpopkontrzaehlEinheitWertes { - totalCount - } - } - `, - variables: { - filter: tpopkontrzaehlEinheitWerteGqlFilterForTree, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createTpopkontrzaehlsQuery.js b/src/modules/createTpopkontrzaehlsQuery.js deleted file mode 100644 index 5179b0075d..0000000000 --- a/src/modules/createTpopkontrzaehlsQuery.js +++ /dev/null @@ -1,43 +0,0 @@ -import { gql } from '@apollo/client' - -export const createTpopkontrzaehlsQuery = ({ - tpopkontrId, - tpopkontrzaehlGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: [ - 'treeTpopfeldkontrzaehl', - tpopkontrId, - tpopkontrzaehlGqlFilterForTree, - ], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeTpopfeldkontrzaehlsQuery( - $tpopkontrId: UUID! - $tpopkontrzaehlsFilter: TpopkontrzaehlFilter! - ) { - tpopkontrById(id: $tpopkontrId) { - id - tpopkontrzaehlsByTpopkontrId( - filter: $tpopkontrzaehlsFilter - orderBy: LABEL_ASC - ) { - nodes { - id - label - } - } - tpopkontrzaehlsCount: tpopkontrzaehlsByTpopkontrId { - totalCount - } - } - } - `, - variables: { - tpopkontrzaehlsFilter: tpopkontrzaehlGqlFilterForTree, - tpopkontrId, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createTpopmassnbersQuery.js b/src/modules/createTpopmassnbersQuery.js deleted file mode 100644 index 289e2f1a1c..0000000000 --- a/src/modules/createTpopmassnbersQuery.js +++ /dev/null @@ -1,39 +0,0 @@ -import { gql } from '@apollo/client' - -export const createTpopmassnbersQuery = ({ - tpopId, - tpopmassnberGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: ['treeTpopmassnber', tpopId, tpopmassnberGqlFilterForTree], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeTpopmassnbersQuery( - $tpopId: UUID! - $tpopmassnbersFilter: TpopmassnberFilter! - ) { - tpopById(id: $tpopId) { - id - tpopmassnbersByTpopId( - filter: $tpopmassnbersFilter - orderBy: [LABEL_ASC] - ) { - nodes { - id - label - } - } - tpopmassnbersCount: tpopmassnbersByTpopId { - totalCount - } - } - } - `, - variables: { - tpopmassnbersFilter: tpopmassnberGqlFilterForTree, - tpopId, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createTpopmassnsQuery.js b/src/modules/createTpopmassnsQuery.js deleted file mode 100644 index fdd6dc7d46..0000000000 --- a/src/modules/createTpopmassnsQuery.js +++ /dev/null @@ -1,39 +0,0 @@ -import { gql } from '@apollo/client' - -export const createTpopmassnsQuery = ({ - tpopId, - tpopmassnGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: ['treeTpopmassn', tpopId, tpopmassnGqlFilterForTree], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeTpopmassnsQuery( - $tpopId: UUID! - $tpopmassnsFilter: TpopmassnFilter! - ) { - tpopById(id: $tpopId) { - id - tpopmassnsByTpopId( - filter: $tpopmassnsFilter - orderBy: [JAHR_ASC, DATUM_ASC] - ) { - nodes { - id - label - } - } - tpopmassnsCount: tpopmassnsByTpopId { - totalCount - } - } - } - `, - variables: { - tpopmassnsFilter: tpopmassnGqlFilterForTree, - tpopId, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createTpopsQuery.js b/src/modules/createTpopsQuery.js deleted file mode 100644 index 9ca461c181..0000000000 --- a/src/modules/createTpopsQuery.js +++ /dev/null @@ -1,38 +0,0 @@ -import { gql } from '@apollo/client' - -export const createTpopsQuery = ({ - popId, - tpopGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: ['treeTpop', popId, tpopGqlFilterForTree], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeTpopQuery($popId: UUID!, $tpopsFilter: TpopFilter!) { - popById(id: $popId) { - id - tpopsByPopId( - filter: $tpopsFilter - orderBy: [NR_ASC, FLURNAME_ASC] - ) { - totalCount - nodes { - id - label - status - } - } - tpopsCount: tpopsByPopId { - totalCount - } - } - } - `, - variables: { - tpopsFilter: tpopGqlFilterForTree, - popId, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createUsersQuery.js b/src/modules/createUsersQuery.js deleted file mode 100644 index cedfe18abf..0000000000 --- a/src/modules/createUsersQuery.js +++ /dev/null @@ -1,23 +0,0 @@ -import { gql } from '@apollo/client' - -export const createUsersQuery = ({ userGqlFilterForTree, apolloClient }) => ({ - queryKey: ['treeUser', userGqlFilterForTree], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeUsersQuery($usersFilter: UserFilter!) { - allUsers(filter: $usersFilter, orderBy: LABEL_ASC) { - nodes { - id - label - } - } - totalCount: allUsers { - totalCount - } - } - `, - variables: { usersFilter: userGqlFilterForTree }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createZielbersQuery.js b/src/modules/createZielbersQuery.js deleted file mode 100644 index 10c966d4fd..0000000000 --- a/src/modules/createZielbersQuery.js +++ /dev/null @@ -1,33 +0,0 @@ -import { gql } from '@apollo/client' - -export const createZielbersQuery = ({ - zielId, - zielberGqlFilterForTree, - apolloClient, -}) => ({ - queryKey: ['treeZielber', zielId, zielberGqlFilterForTree], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeZielbersQuery($zielId: UUID!, $filter: ZielberFilter!) { - zielById(id: $zielId) { - id - zielbersByZielId(filter: $filter, orderBy: LABEL_ASC) { - nodes { - id - label - } - } - zielbersCount: zielbersByZielId { - totalCount - } - } - } - `, - variables: { - filter: zielberGqlFilterForTree, - zielId, - }, - fetchPolicy: 'no-cache', - }), -}) diff --git a/src/modules/createZielsQuery.js b/src/modules/createZielsQuery.js deleted file mode 100644 index 169f0649ee..0000000000 --- a/src/modules/createZielsQuery.js +++ /dev/null @@ -1,49 +0,0 @@ -import { gql } from '@apollo/client' - -export const createZielsQuery = ({ - apId, - zielGqlFilterForTree, - apolloClient, - jahr, -}) => { - const filter = - jahr ? - { ...zielGqlFilterForTree, jahr: { equalTo: +jahr } } - : zielGqlFilterForTree - const countFilter = - jahr ? { jahr: { equalTo: +jahr } } : { id: { isNull: false } } - - return { - queryKey: ['treeZiel', apId, zielGqlFilterForTree, jahr], - queryFn: () => - apolloClient.query({ - query: gql` - query TreeZielsQuery( - $apId: UUID! - $filter: ZielFilter! - $countFilter: ZielFilter! - ) { - apById(id: $apId) { - id - zielsByApId(filter: $filter, orderBy: [JAHR_ASC, LABEL_ASC]) { - nodes { - id - label - jahr - } - } - zielsCount: zielsByApId(filter: $countFilter) { - totalCount - } - } - } - `, - variables: { - filter, - countFilter, - apId, - }, - fetchPolicy: 'no-cache', - }), - } -} diff --git a/src/modules/getDataArrayFromExportObjects.worker.js b/src/modules/getDataArrayFromExportObjects.worker.js index 79605bfb5f..0ed0e9c78b 100644 --- a/src/modules/getDataArrayFromExportObjects.worker.js +++ b/src/modules/getDataArrayFromExportObjects.worker.js @@ -1,4 +1,4 @@ -export function getDataArrayFromExportObjects(exportObjects) { +export function GetDataArrayFromExportObjectsWorker(exportObjects) { const dataArray = [] // first the field names: dataArray.push(Object.keys(exportObjects[0])) diff --git a/src/modules/getDataArrayFromExportObjectsWorker.js b/src/modules/getDataArrayFromExportObjectsWorker.js index e22e598f73..042dfbcfd6 100644 --- a/src/modules/getDataArrayFromExportObjectsWorker.js +++ b/src/modules/getDataArrayFromExportObjectsWorker.js @@ -1,4 +1,4 @@ -import GetDataArrayFromExportObjectsWorker from './getDataArrayFromExportObjects.worker.js' +import { GetDataArrayFromExportObjectsWorker } from './getDataArrayFromExportObjects.worker.js' export const getDataArrayFromExportObjectsWorker = typeof window === 'object' && new GetDataArrayFromExportObjectsWorker() diff --git a/src/modules/isMobilePhone.js b/src/modules/isMobilePhone.js index a0290f7b20..3737254a85 100644 --- a/src/modules/isMobilePhone.js +++ b/src/modules/isMobilePhone.js @@ -1,5 +1,6 @@ // from: https://stackoverflow.com/a/11381730/712005 /* eslint-disable */ +// not in use since using min width of 1000px export const isMobilePhone = () => { let check = false const a = navigator.userAgent || navigator.vendor || window.opera diff --git a/src/modules/moveTo/index.js b/src/modules/moveTo/index.js index 750f0bbc37..9490cd3d04 100644 --- a/src/modules/moveTo/index.js +++ b/src/modules/moveTo/index.js @@ -88,22 +88,27 @@ export const moveTo = async ({ tanstackQueryClient.invalidateQueries({ queryKey: [`treeApFolders`], }) + tanstackQueryClient.invalidateQueries({ + queryKey: ['treeAp'], + }) } if (table === 'tpop') { tanstackQueryClient.invalidateQueries({ queryKey: [`treeTpop`], }) - tanstackQueryClient.invalidateQueries({ queryKey: ['treePopFolders'], }) + tanstackQueryClient.invalidateQueries({ + queryKey: ['treePop'], + }) } if (table === 'tpopmassn') { tanstackQueryClient.invalidateQueries({ queryKey: [`treeTpopmassn`], }) tanstackQueryClient.invalidateQueries({ - queryKey: [`treeTpopFolders`], + queryKey: [`treeTpop`], }) } if (table === 'tpopfeldkontr') { @@ -111,7 +116,7 @@ export const moveTo = async ({ queryKey: [`treeTpopfeldkontr`], }) tanstackQueryClient.invalidateQueries({ - queryKey: [`treeTpopFolders`], + queryKey: [`treeTpop`], }) } if (table === 'tpopfreiwkontr') { @@ -119,7 +124,7 @@ export const moveTo = async ({ queryKey: [`treeTpopfreiwkontr`], }) tanstackQueryClient.invalidateQueries({ - queryKey: [`treeTpopFolders`], + queryKey: [`treeTpop`], }) } return diff --git a/src/modules/react-contextmenu/AbstractMenu.js b/src/modules/react-contextmenu/AbstractMenu.js index 36422e8028..1b34657b94 100644 --- a/src/modules/react-contextmenu/AbstractMenu.js +++ b/src/modules/react-contextmenu/AbstractMenu.js @@ -39,7 +39,6 @@ function _inherits(subClass, superClass) { } import { Component } from 'react' -import PropTypes from 'prop-types' import MenuItem from './MenuItem.js' @@ -70,10 +69,6 @@ var AbstractMenu = (function (_Component) { return AbstractMenu })(Component) -AbstractMenu.propTypes = { - children: PropTypes.node.isRequired, -} - var _initialiseProps = function _initialiseProps() { var _this2 = this diff --git a/src/modules/react-contextmenu/ContextMenu.js b/src/modules/react-contextmenu/ContextMenu.js index c47cc31bb7..35f6f3096e 100644 --- a/src/modules/react-contextmenu/ContextMenu.js +++ b/src/modules/react-contextmenu/ContextMenu.js @@ -68,7 +68,6 @@ function _inherits(subClass, superClass) { } import React from 'react' -import PropTypes from 'prop-types' import cx from 'classnames' import assign from 'object-assign' @@ -122,7 +121,7 @@ var ContextMenu = (function (_AbstractMenu) { _this.setState({ isVisible: true, x: x, y: y }) _this.registerHandlers() - callIfExists(_this.props.onShow, e) + callIfExists(_this.props.onShow ?? (() => null), e) } _this.handleHide = function (e) { @@ -136,7 +135,7 @@ var ContextMenu = (function (_AbstractMenu) { selectedItem: null, forceSubMenuOpen: false, }) - callIfExists(_this.props.onHide, e) + callIfExists(_this.props.onHide ?? (() => null), e) } } @@ -148,9 +147,9 @@ var ContextMenu = (function (_AbstractMenu) { event.preventDefault() callIfExists( - _this.props.onMouseLeave, + _this.props.onMouseLeave ?? (() => null), event, - assign({}, _this.props.data, store.data), + assign({}, _this.props.data ?? {}, store.data), store.target, ) @@ -330,8 +329,8 @@ var ContextMenu = (function (_AbstractMenu) { value: function render() { var _props = this.props, children = _props.children, - className = _props.className, - style = _props.style + className = _props.className ?? '', + style = _props.style ?? {} var isVisible = this.state.isVisible var inlineStyle = assign({}, style, { @@ -341,7 +340,7 @@ var ContextMenu = (function (_AbstractMenu) { }) var menuClassnames = cx( cssClasses.menu, - className, + className ?? '', _defineProperty({}, cssClasses.menuVisible, isVisible), ) @@ -365,39 +364,4 @@ var ContextMenu = (function (_AbstractMenu) { return ContextMenu })(AbstractMenu) -ContextMenu.propTypes = { - id: PropTypes.string.isRequired, - children: PropTypes.node.isRequired, - data: PropTypes.object, - className: PropTypes.string, - hideOnLeave: PropTypes.bool, - rtl: PropTypes.bool, - onHide: PropTypes.func, - onMouseLeave: PropTypes.func, - onShow: PropTypes.func, - preventHideOnContextMenu: PropTypes.bool, - preventHideOnResize: PropTypes.bool, - preventHideOnScroll: PropTypes.bool, - style: PropTypes.object, -} -ContextMenu.defaultProps = { - className: '', - data: {}, - hideOnLeave: false, - rtl: false, - onHide: function onHide() { - return null - }, - onMouseLeave: function onMouseLeave() { - return null - }, - onShow: function onShow() { - return null - }, - - preventHideOnContextMenu: false, - preventHideOnResize: false, - preventHideOnScroll: false, - style: {}, -} export default ContextMenu diff --git a/src/modules/react-contextmenu/ContextMenuTrigger.js b/src/modules/react-contextmenu/ContextMenuTrigger.js index fd7534125f..7717566210 100644 --- a/src/modules/react-contextmenu/ContextMenuTrigger.js +++ b/src/modules/react-contextmenu/ContextMenuTrigger.js @@ -56,7 +56,6 @@ function _inherits(subClass, superClass) { } import { Component } from 'react' -import PropTypes from 'prop-types' import cx from 'classnames' import assign from 'object-assign' @@ -96,60 +95,60 @@ var ContextMenuTrigger = (function (_Component) { _this)), (_this.touchHandled = false), (_this.handleMouseDown = function (event) { - if (_this.props.holdToDisplay >= 0 && event.button === 0) { + if ((_this.props.holdToDisplay ?? 1000) >= 0 && event.button === 0) { event.persist() event.stopPropagation() _this.mouseDownTimeoutId = setTimeout(function () { return _this.handleContextClick(event) - }, _this.props.holdToDisplay) + }, _this.props.holdToDisplay ?? 1000) } - callIfExists(_this.props.attributes.onMouseDown, event) + callIfExists(_this.props.attributes?.onMouseDown, event) }), (_this.handleMouseUp = function (event) { if (event.button === 0) { clearTimeout(_this.mouseDownTimeoutId) } - callIfExists(_this.props.attributes.onMouseUp, event) + callIfExists(_this.props.attributes?.onMouseUp, event) }), (_this.handleMouseOut = function (event) { if (event.button === 0) { clearTimeout(_this.mouseDownTimeoutId) } - callIfExists(_this.props.attributes.onMouseOut, event) + callIfExists(_this.props.attributes?.onMouseOut, event) }), (_this.handleTouchstart = function (event) { _this.touchHandled = false - if (_this.props.holdToDisplay >= 0) { + if ((_this.props.holdToDisplay ?? 1000) >= 0) { event.persist() event.stopPropagation() _this.touchstartTimeoutId = setTimeout(function () { _this.handleContextClick(event) _this.touchHandled = true - }, _this.props.holdToDisplay) + }, _this.props.holdToDisplay ?? 1000) } - callIfExists(_this.props.attributes.onTouchStart, event) + callIfExists(_this.props.attributes?.onTouchStart, event) }), (_this.handleTouchEnd = function (event) { if (_this.touchHandled) { event.preventDefault() } clearTimeout(_this.touchstartTimeoutId) - callIfExists(_this.props.attributes.onTouchEnd, event) + callIfExists(_this.props.attributes?.onTouchEnd, event) }), (_this.handleContextMenu = function (event) { - if (event.button === _this.props.mouseButton) { + if (event.button === 2) { _this.handleContextClick(event) } - callIfExists(_this.props.attributes.onContextMenu, event) + callIfExists(_this.props.attributes?.onContextMenu, event) }), (_this.handleMouseClick = function (event) { - if (event.button === _this.props.mouseButton) { + if (event.button === 2) { _this.handleContextClick(event) } - callIfExists(_this.props.attributes.onClick, event) + callIfExists(_this.props.attributes?.onClick, event) }), (_this.handleContextClick = function (event) { if (_this.props.disable) return @@ -170,7 +169,10 @@ var ContextMenuTrigger = (function (_Component) { hideMenu() - var data = callIfExists(_this.props.collect, _this.props) + var data = callIfExists( + _this.props.collect ?? (() => null), + _this.props, + ) var showMenuConfig = { position: { x: x, y: y }, target: _this.elem, @@ -204,8 +206,8 @@ var ContextMenuTrigger = (function (_Component) { key: 'render', value: function render() { var _props = this.props, - renderTag = _props.renderTag, - attributes = _props.attributes, + renderTag = _props.renderTag ?? 'div', + attributes = _props.attributes ?? {}, children = _props.children var newAttrs = assign({}, attributes, { @@ -228,31 +230,4 @@ var ContextMenuTrigger = (function (_Component) { return ContextMenuTrigger })(Component) -ContextMenuTrigger.propTypes = { - id: PropTypes.string.isRequired, - children: PropTypes.node.isRequired, - attributes: PropTypes.object, - collect: PropTypes.func, - disable: PropTypes.bool, - holdToDisplay: PropTypes.number, - posX: PropTypes.number, - posY: PropTypes.number, - renderTag: PropTypes.elementType, - mouseButton: PropTypes.number, - disableIfShiftIsPressed: PropTypes.bool, -} -ContextMenuTrigger.defaultProps = { - attributes: {}, - collect: function collect() { - return null - }, - - disable: false, - holdToDisplay: 1000, - renderTag: 'div', - posX: 0, - posY: 0, - mouseButton: 2, // 0 is left click, 2 is right click - disableIfShiftIsPressed: false, -} export default ContextMenuTrigger diff --git a/src/modules/react-contextmenu/MenuItem.js b/src/modules/react-contextmenu/MenuItem.js index aecdf08eb2..6e955a740e 100644 --- a/src/modules/react-contextmenu/MenuItem.js +++ b/src/modules/react-contextmenu/MenuItem.js @@ -84,7 +84,6 @@ function _inherits(subClass, superClass) { } import { Component } from 'react' -import PropTypes from 'prop-types' import cx from 'classnames' import assign from 'object-assign' @@ -129,9 +128,9 @@ var MenuItem = (function (_Component) { if (_this.props.disabled || _this.props.divider) return callIfExists( - _this.props.onClick, + _this.props.onClick ?? (() => null), event, - assign({}, _this.props.data, store.data), + assign({}, _this.props.data ?? {}, store.data), store.target, ) @@ -152,12 +151,12 @@ var MenuItem = (function (_Component) { _this2 = this var _props = this.props, - attributes = _props.attributes, - children = _props.children, - className = _props.className, - disabled = _props.disabled, - divider = _props.divider, - selected = _props.selected + attributes = _props.attributes ?? {}, + children = _props.children ?? null, + className = _props.className ?? '', + disabled = _props.disabled ?? false, + divider = _props.divider ?? false, + selected = _props.selected ?? false var menuItemClassNames = cx( className, @@ -193,8 +192,8 @@ var MenuItem = (function (_Component) { ref: function ref(_ref2) { _this2.ref = _ref2 }, - onMouseMove: this.props.onMouseMove, - onMouseLeave: this.props.onMouseLeave, + onMouseMove: this.props.onMouseMove ?? (() => null), + onMouseLeave: this.props.onMouseLeave ?? (() => null), onTouchEnd: this.handleClick, onClick: this.handleClick, }), @@ -207,37 +206,4 @@ var MenuItem = (function (_Component) { return MenuItem })(Component) -MenuItem.propTypes = { - attributes: PropTypes.object, - children: PropTypes.node, - className: PropTypes.string, - data: PropTypes.object, - disabled: PropTypes.bool, - divider: PropTypes.bool, - onClick: PropTypes.func, - onMouseLeave: PropTypes.func, - onMouseMove: PropTypes.func, - preventClose: PropTypes.bool, - selected: PropTypes.bool, -} -MenuItem.defaultProps = { - attributes: {}, - children: null, - className: '', - data: {}, - disabled: false, - divider: false, - onClick: function onClick() { - return null - }, - - onMouseMove: function onMouseMove() { - return null - }, - onMouseLeave: function onMouseLeave() { - return null - }, - preventClose: false, - selected: false, -} export default MenuItem diff --git a/src/modules/react-contextmenu/SubMenu.js b/src/modules/react-contextmenu/SubMenu.js index 40e6a5bdf7..1c2d316408 100644 --- a/src/modules/react-contextmenu/SubMenu.js +++ b/src/modules/react-contextmenu/SubMenu.js @@ -82,7 +82,6 @@ function _inherits(subClass, superClass) { } import React from 'react' -import PropTypes from 'prop-types' import cx from 'classnames' import assign from 'object-assign' @@ -159,7 +158,7 @@ var SubMenu = (function (_AbstractMenu) { } if (_this.props.forceOpen) { - _this.props.forceClose() + _this.props.forceClose?.() } _this.setState({ visible: false, selectedItem: null }) _this.unregisterHandlers() @@ -192,7 +191,7 @@ var SubMenu = (function (_AbstractMenu) { visible: true, selectedItem: null, }) - }, _this.props.hoverDelay) + }, _this.props.hoverDelay ?? 500) } _this.handleMouseLeave = function () { @@ -205,7 +204,7 @@ var SubMenu = (function (_AbstractMenu) { visible: false, selectedItem: null, }) - }, _this.props.hoverDelay) + }, _this.props.hoverDelay ?? 500) } _this.menuRef = function (c) { @@ -219,7 +218,7 @@ var SubMenu = (function (_AbstractMenu) { _this.registerHandlers = function () { document.removeEventListener( 'keydown', - _this.props.parentKeyNavigationHandler, + _this.props.parentKeyNavigationHandler ?? (() => null), ) document.addEventListener('keydown', _this.handleKeyNavigation) } @@ -229,7 +228,7 @@ var SubMenu = (function (_AbstractMenu) { if (!dismounting) { document.addEventListener( 'keydown', - _this.props.parentKeyNavigationHandler, + _this.props.parentKeyNavigationHandler ?? (() => null), ) } } @@ -250,7 +249,6 @@ var SubMenu = (function (_AbstractMenu) { { key: 'getSubMenuType', value: function getSubMenuType() { - return SubMenu }, }, @@ -259,9 +257,9 @@ var SubMenu = (function (_AbstractMenu) { value: function shouldComponentUpdate(nextProps, nextState) { this.isVisibilityChange = (this.state.visible !== nextState.visible || - this.props.forceOpen !== nextProps.forceOpen) && + (this.props.forceOpen ?? false) !== nextProps.forceOpen) && !(this.state.visible && nextProps.forceOpen) && - !(this.props.forceOpen && nextState.visible) + !((this.props.forceOpen ?? false) && nextState.visible) return true }, }, @@ -271,7 +269,7 @@ var SubMenu = (function (_AbstractMenu) { var _this2 = this if (!this.isVisibilityChange) return - if (this.props.forceOpen || this.state.visible) { + if ((this.props.forceOpen ?? false) || this.state.visible) { var wrapper = window.requestAnimationFrame || setTimeout wrapper(function () { var styles = @@ -331,10 +329,10 @@ var SubMenu = (function (_AbstractMenu) { var _props = this.props, children = _props.children, - attributes = _props.attributes, - disabled = _props.disabled, + attributes = _props.attributes ?? {}, + disabled = _props.disabled ?? false, title = _props.title, - selected = _props.selected + selected = _props.selected ?? false var visible = this.state.visible var menuProps = { @@ -372,8 +370,8 @@ var SubMenu = (function (_AbstractMenu) { ), _cx), ), - onMouseMove: this.props.onMouseMove, - onMouseOut: this.props.onMouseOut, + onMouseMove: this.props.onMouseMove ?? (() => null), + onMouseOut: this.props.onMouseOut ?? (() => null), onClick: this.handleClick, } var subMenuProps = { @@ -384,7 +382,7 @@ var SubMenu = (function (_AbstractMenu) { top: 0, left: '100%', }, - className: cx(cssClasses.menu, this.props.className), + className: cx(cssClasses.menu, this.props.className ?? ''), } return React.createElement( @@ -412,40 +410,4 @@ var SubMenu = (function (_AbstractMenu) { return SubMenu })(AbstractMenu) -SubMenu.propTypes = { - children: PropTypes.node.isRequired, - attributes: PropTypes.object, - title: PropTypes.node.isRequired, - className: PropTypes.string, - disabled: PropTypes.bool, - hoverDelay: PropTypes.number, - rtl: PropTypes.bool, - selected: PropTypes.bool, - onMouseMove: PropTypes.func, - onMouseOut: PropTypes.func, - forceOpen: PropTypes.bool, - forceClose: PropTypes.func, - parentKeyNavigationHandler: PropTypes.func, -} -SubMenu.defaultProps = { - disabled: false, - hoverDelay: 500, - attributes: {}, - className: '', - rtl: false, - selected: false, - onMouseMove: function onMouseMove() { - return null - }, - onMouseOut: function onMouseOut() { - return null - }, - forceOpen: false, - forceClose: function forceClose() { - return null - }, - parentKeyNavigationHandler: function parentKeyNavigationHandler() { - return null - }, -} export default SubMenu diff --git a/src/modules/react-contextmenu/connectMenu.js b/src/modules/react-contextmenu/connectMenu.js index 357b957da2..e51fa204ed 100644 --- a/src/modules/react-contextmenu/connectMenu.js +++ b/src/modules/react-contextmenu/connectMenu.js @@ -82,14 +82,22 @@ function _toConsumableArray(arr) { import { Component } from 'react' -import ContextMenuTrigger from './ContextMenuTrigger.js' import listener from './globalEventListener.js' // collect ContextMenuTrigger's expected props to NOT pass them on as part of the context -var ignoredTriggerProps = [].concat( - _toConsumableArray(Object.keys(ContextMenuTrigger.propTypes)), - ['children'], -) +var ignoredTriggerProps = [ + 'id', + 'children', + 'attributes', + 'collect', + 'disable', + 'holdToDispla', + 'posX', + 'posY', + 'renderTag', + 'mouseButton', + 'disableIfShiftIsPressed', +] // expect the id of the menu to be responsible for as outer parameter export default function (menuId) { diff --git a/src/modules/useAdressesNavData.js b/src/modules/useAdressesNavData.js new file mode 100644 index 0000000000..acade9eec8 --- /dev/null +++ b/src/modules/useAdressesNavData.js @@ -0,0 +1,62 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' + +import { MobxContext } from '../mobxContext.js' + +export const useAdressesNavData = () => { + const apolloClient = useApolloClient() + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treeAdresse', store.tree.adresseGqlFilterForTree], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeAdressesQuery($adressesFilter: AdresseFilter!) { + allAdresses(filter: $adressesFilter, orderBy: LABEL_ASC) { + nodes { + id + label + } + } + totalCount: allAdresses { + totalCount + } + } + `, + variables: { + adressesFilter: store.tree.adresseGqlFilterForTree, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.adresseGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + const count = data?.data?.allAdresses?.nodes?.length ?? 0 + const totalCount = data?.data?.totalCount?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: 'Adressen', + listFilter: 'adresse', + url: `/Daten/Werte-Listen/Adressen`, + label: `Adressen (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus: (data?.data?.allAdresses?.nodes ?? []).map((p) => ({ + id: p.id, + label: p.label, + })), + }), + [count, data?.data?.allAdresses?.nodes, isLoading, totalCount], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useApNavData.js b/src/modules/useApNavData.js new file mode 100644 index 0000000000..d99873bd35 --- /dev/null +++ b/src/modules/useApNavData.js @@ -0,0 +1,461 @@ +import { useMemo, useContext, useEffect, useState, useCallback } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { useParams } from 'react-router' +import { reaction } from 'mobx' +import countBy from 'lodash/countBy' + +import { MobxContext } from '../mobxContext.js' +import { PopMapIcon } from '../components/NavElements/PopMapIcon.jsx' +import { BeobnichtbeurteiltMapIcon } from '../components/NavElements/BeobnichtbeurteiltMapIcon.jsx' +import { BeobnichtzuzuordnenMapIcon } from '../components/NavElements/BeobnichtzuzuordnenMapIcon.jsx' +import { useProjekteTabs } from './useProjekteTabs.js' + +export const useApNavData = (props) => { + const apolloClient = useApolloClient() + const { projId, apId: apIdFromParams } = useParams() + const apId = props?.apId ?? apIdFromParams + + const [projekteTabs] = useProjekteTabs() + const karteIsVisible = projekteTabs.includes('karte') + + const store = useContext(MobxContext) + + const showPopIcon = + store.activeApfloraLayers?.includes('pop') && karteIsVisible + const showBeobnichtbeurteiltIcon = + store.activeApfloraLayers?.includes('beobNichtBeurteilt') && karteIsVisible + const showBeobnichtzuzuordnenIcon = + store.activeApfloraLayers?.includes('beobNichtZuzuordnen') && karteIsVisible + const [, setRerenderer] = useState(0) + const rerender = useCallback(() => setRerenderer((prev) => prev + 1), []) + + const allBeobNichtZuzuordnenFilter = useMemo( + () => ({ + nichtZuordnen: { equalTo: true }, + aeTaxonomyByArtId: { + apartsByArtId: { + some: { + apByApId: { + id: { equalTo: apId }, + }, + }, + }, + }, + }), + [apId], + ) + const allBeobNichtBeurteiltFilter = useMemo( + () => ({ + tpopId: { isNull: true }, + nichtZuordnen: { equalTo: false }, + aeTaxonomyByArtId: { + apartsByArtId: { + some: { + apByApId: { + id: { equalTo: apId }, + }, + }, + }, + }, + }), + [apId], + ) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: [ + 'treeAp', + projId, + apId, + store.tree.popGqlFilterForTree, + store.tree.zielGqlFilterForTree, + store.tree.erfkritGqlFilterForTree, + store.tree.apberGqlFilterForTree, + store.tree.apartGqlFilterForTree, + store.tree.assozartGqlFilterForTree, + store.tree.ekfrequenzGqlFilterForTree, + store.tree.ekzaehleinheitGqlFilterForTree, + store.tree.beobNichtBeurteiltGqlFilterForTree, + store.tree.beobNichtZuzuordnenGqlFilterForTree, + ], + queryFn: () => + apolloClient.query({ + query: gql` + query NavApQuery( + $apId: UUID! + $popFilter: PopFilter! + $zielFilter: ZielFilter! + $erfkritFilter: ErfkritFilter! + $apberFilter: ApberFilter! + $apartFilter: ApartFilter! + $assozartFilter: AssozartFilter! + $ekfrequenzFilter: EkfrequenzFilter! + $ekzaehleinheitFilter: EkzaehleinheitFilter! + $beobNichtBeurteiltFilter: BeobFilter! + $beobNichtZuzuordnenFilter: BeobFilter! + $allBeobNichtZuzuordnenFilter: BeobFilter! + $allBeobNichtBeurteiltFilter: BeobFilter! + ) { + apById(id: $apId) { + id + label + popsByApId { + totalCount + } + filteredPops: popsByApId(filter: $popFilter) { + totalCount + } + zielsByApId { + totalCount + nodes { + id + jahr + } + } + filteredZiels: zielsByApId(filter: $zielFilter) { + totalCount + nodes { + id + jahr + } + } + erfkritsByApId { + totalCount + } + filteredErfkrits: erfkritsByApId(filter: $erfkritFilter) { + totalCount + } + apbersByApId { + totalCount + } + filteredApbers: apbersByApId(filter: $apberFilter) { + totalCount + } + apartsByApId { + totalCount + } + filteredAparts: apartsByApId(filter: $apartFilter) { + totalCount + } + assozartsByApId { + totalCount + } + filteredAssozarts: assozartsByApId(filter: $assozartFilter) { + totalCount + } + ekfrequenzsByApId { + totalCount + } + filteredEkfrequenzs: ekfrequenzsByApId( + filter: $ekfrequenzFilter + ) { + totalCount + } + ekzaehleinheitsByApId { + totalCount + } + filteredEkzaehleinheits: ekzaehleinheitsByApId( + filter: $ekzaehleinheitFilter + ) { + totalCount + } + apFilesByApId { + totalCount + } + } + allApHistories(filter: { id: { equalTo: $apId } }) { + totalCount + } + beobsNichtBeurteilt: allBeobs( + filter: $allBeobNichtBeurteiltFilter + ) { + totalCount + } + filteredBeobsNichtBeurteilt: allBeobs( + filter: $beobNichtBeurteiltFilter + ) { + totalCount + } + beobsNichtZuzuordnen: allBeobs( + filter: $allBeobNichtZuzuordnenFilter + ) { + totalCount + } + filteredBeobsNichtZuzuordnen: allBeobs( + filter: $beobNichtZuzuordnenFilter + ) { + totalCount + } + } + `, + variables: { + apId, + popFilter: store.tree.popGqlFilterForTree, + zielFilter: store.tree.zielGqlFilterForTree, + erfkritFilter: store.tree.erfkritGqlFilterForTree, + apberFilter: store.tree.apberGqlFilterForTree, + apartFilter: store.tree.apartGqlFilterForTree, + assozartFilter: store.tree.assozartGqlFilterForTree, + ekfrequenzFilter: store.tree.ekfrequenzGqlFilterForTree, + ekzaehleinheitFilter: store.tree.ekzaehleinheitGqlFilterForTree, + beobNichtBeurteiltFilter: { + ...store.tree.beobNichtBeurteiltGqlFilterForTree, + aeTaxonomyByArtId: { + apartsByArtId: { + some: { + apByApId: { + id: { equalTo: apId }, + }, + }, + }, + }, + }, + beobNichtZuzuordnenFilter: { + ...store.tree.beobNichtZuzuordnenGqlFilterForTree, + aeTaxonomyByArtId: { + apartsByArtId: { + some: { + apByApId: { + id: { equalTo: apId }, + }, + }, + }, + }, + }, + allBeobNichtZuzuordnenFilter, + allBeobNichtBeurteiltFilter, + }, + fetchPolicy: 'no-cache', + }), + }) + useEffect( + () => reaction(() => store.tree.popGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.tree.zielGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.tree.erfkritGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.tree.apberGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.tree.apartGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.tree.assozartGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.tree.ekfrequenzGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.tree.ekzaehleinheitGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => + reaction(() => store.tree.beobNichtBeurteiltGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => + reaction(() => store.tree.beobNichtZuzuordnenGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.activeApfloraLayers.slice(), rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const label = data?.data?.apById?.label + const popsCount = data?.data?.apById?.popsByApId?.totalCount ?? 0 + const filteredPopsCount = data?.data?.apById?.filteredPops?.totalCount ?? 0 + const apZiels = data?.data?.apById?.zielsByApId?.nodes ?? [] + const apZielJahrs = countBy(apZiels, 'jahr') + const apZielJahrsCount = Object.keys(apZielJahrs).length + const filteredApZiels = data?.data?.apById?.filteredZiels?.nodes ?? [] + const filteredApZielJahrs = countBy(filteredApZiels, 'jahr') + const filteredApZielJahrsCount = Object.keys(filteredApZielJahrs).length + const erfkritsCount = data?.data?.apById?.erfkritsByApId?.totalCount ?? 0 + const filteredErfkritsCount = + data?.data?.apById?.filteredErfkrits?.totalCount ?? 0 + const apbersCount = data?.data?.apById?.apbersByApId?.totalCount ?? 0 + const filteredApbersCount = + data?.data?.apById?.filteredApbers?.totalCount ?? 0 + const apartsCount = data?.data?.apById?.apartsByApId?.totalCount ?? 0 + const filteredApartsCount = + data?.data?.apById?.filteredAparts?.totalCount ?? 0 + const assozartsCount = data?.data?.apById?.assozartsByApId?.totalCount ?? 0 + const filteredAssozartsCount = + data?.data?.apById?.filteredAssozarts?.totalCount ?? 0 + const ekfrequenzsCount = + data?.data?.apById?.ekfrequenzsByApId?.totalCount ?? 0 + const filteredEkfrequenzsCount = + data?.data?.apById?.filteredEkfrequenzs?.totalCount ?? 0 + const ekzaehleinheitsCount = + data?.data?.apById?.ekzaehleinheitsByApId?.totalCount ?? 0 + const filteredEkzaehleinheitsCount = + data?.data?.apById?.filteredEkzaehleinheits?.totalCount ?? 0 + const beobsNichtBeurteiltCount = + data?.data?.beobsNichtBeurteilt?.totalCount ?? 0 + const filteredBeobsNichtBeurteiltCount = + data?.data?.filteredBeobsNichtBeurteilt?.totalCount ?? 0 + const beobsNichtZuzuordnenCount = + data?.data?.beobsNichtZuzuordnen?.totalCount ?? 0 + const filteredBeobsNichtZuzuordnenCount = + data?.data?.filteredBeobsNichtZuzuordnen?.totalCount ?? 0 + const filesCount = data?.data?.apById?.apFilesByApId?.totalCount ?? 0 + const historiesCount = data?.data?.allApHistories?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: apId, + url: `/Daten/Projekte/${projId}/Arten/${apId}`, + label, + // leave totalCount undefined as the menus are folders + menus: [ + { + id: 'Art', + label: `Art`, + }, + { + id: 'Populationen', + label: `Populationen (${isLoading ? '...' : `${filteredPopsCount}/${popsCount}`})`, + count: popsCount, + labelLeftElements: showPopIcon ? [PopMapIcon] : undefined, + }, + { + id: 'AP-Ziele', + label: `AP-Ziele Jahre (${isLoading ? '...' : `${filteredApZielJahrsCount}/${apZielJahrsCount}`})`, + count: apZielJahrsCount, + }, + { + id: 'AP-Erfolgskriterien', + label: `AP-Erfolgskriterien (${isLoading ? '...' : `${filteredErfkritsCount}/${erfkritsCount}`})`, + count: erfkritsCount, + }, + { + id: 'AP-Berichte', + label: `AP-Berichte (${isLoading ? '...' : `${filteredApbersCount}/${apbersCount}`})`, + count: apbersCount, + }, + { + id: 'Idealbiotop', + label: `Idealbiotop`, + }, + { + id: 'Taxa', + label: `Taxa (${isLoading ? '...' : `${filteredApartsCount}/${apartsCount}`})`, + count: apartsCount, + }, + { + id: 'assoziierte-Arten', + label: `Assoziierte Arten (${isLoading ? '...' : `${filteredAssozartsCount}/${assozartsCount}`})`, + count: assozartsCount, + }, + { + id: 'EK-Frequenzen', + label: `EK-Frequenzen (${isLoading ? '...' : `${filteredEkfrequenzsCount}/${ekfrequenzsCount}`})`, + count: ekfrequenzsCount, + }, + { + id: 'EK-Zähleinheiten', + label: `EK-Zähleinheiten (${isLoading ? '...' : `${filteredEkzaehleinheitsCount}/${ekzaehleinheitsCount}`})`, + count: ekzaehleinheitsCount, + }, + { + id: 'nicht-beurteilte-Beobachtungen', + label: `Beobachtungen nicht beurteilt (${isLoading ? '...' : `${filteredBeobsNichtBeurteiltCount}/${beobsNichtBeurteiltCount}`})`, + count: beobsNichtBeurteiltCount, + labelLeftElements: + showBeobnichtbeurteiltIcon ? + [BeobnichtbeurteiltMapIcon] + : undefined, + }, + { + id: 'nicht-zuzuordnende-Beobachtungen', + label: `Beobachtungen nicht zuzuordnen (${isLoading ? '...' : `${filteredBeobsNichtZuzuordnenCount}/${beobsNichtZuzuordnenCount}`})`, + count: beobsNichtZuzuordnenCount, + labelLeftElements: + showBeobnichtzuzuordnenIcon ? + [BeobnichtzuzuordnenMapIcon] + : undefined, + }, + { + id: 'Qualitätskontrollen', + label: `Qualitätskontrollen ausführen`, + }, + { + id: 'Qualitätskontrollen-wählen', + label: `Qualitätskontrollen wählen`, + }, + { + id: 'Auswertung', + label: `Auswertung`, + }, + { + id: 'Dateien', + label: `Dateien (${filesCount})`, + count: filesCount, + }, + { + id: 'Historien', + label: `Historien (${historiesCount})`, + count: historiesCount, + }, + ], + }), + [ + apId, + projId, + label, + isLoading, + filteredPopsCount, + popsCount, + showPopIcon, + filteredApZielJahrsCount, + apZielJahrsCount, + filteredErfkritsCount, + erfkritsCount, + filteredApbersCount, + apbersCount, + filteredApartsCount, + apartsCount, + filteredAssozartsCount, + assozartsCount, + filteredEkfrequenzsCount, + ekfrequenzsCount, + filteredEkzaehleinheitsCount, + ekzaehleinheitsCount, + filteredBeobsNichtBeurteiltCount, + beobsNichtBeurteiltCount, + showBeobnichtbeurteiltIcon, + filteredBeobsNichtZuzuordnenCount, + beobsNichtZuzuordnenCount, + showBeobnichtzuzuordnenIcon, + filesCount, + historiesCount, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useApartsNavData.js b/src/modules/useApartsNavData.js new file mode 100644 index 0000000000..07aaf11142 --- /dev/null +++ b/src/modules/useApartsNavData.js @@ -0,0 +1,79 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' +import { useParams } from 'react-router' + +import { MobxContext } from '../mobxContext.js' + +export const useApartsNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treeApart', projId, apId, store.tree.apartGqlFilterForTree], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeApartsQuery($apartsFilter: ApartFilter!, $apId: UUID!) { + apById(id: $apId) { + id + apartsByApId(filter: $apartsFilter, orderBy: LABEL_ASC) { + totalCount + nodes { + id + label + } + } + totalCount: apartsByApId { + totalCount + } + } + } + `, + variables: { + apartsFilter: store.tree.apartGqlFilterForTree, + apId, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.apartGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = data?.data?.apById?.apartsByApId?.nodes?.length ?? 0 + const totalCount = data?.data?.apById?.totalCount?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: 'Taxa', + listFilter: 'apart', + url: `/Daten/Projekte/${projId}/Arten/${apId}/Taxa`, + label: `Taxa (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus: (data?.data?.apById?.apartsByApId?.nodes ?? []).map((p) => ({ + id: p.id, + label: p.label, + })), + }), + [ + apId, + count, + data?.data?.apById?.apartsByApId?.nodes, + isLoading, + projId, + totalCount, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useApbersNavData.js b/src/modules/useApbersNavData.js new file mode 100644 index 0000000000..256f673343 --- /dev/null +++ b/src/modules/useApbersNavData.js @@ -0,0 +1,79 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' +import { useParams } from 'react-router' + +import { MobxContext } from '../mobxContext.js' + +export const useApbersNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treeApber', projId, apId, store.tree.apberGqlFilterForTree], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeApbersQuery($apbersFilter: ApberFilter!, $apId: UUID!) { + apById(id: $apId) { + id + apbersByApId(filter: $apbersFilter, orderBy: LABEL_ASC) { + totalCount + nodes { + id + label + } + } + totalCount: apbersByApId { + totalCount + } + } + } + `, + variables: { + apbersFilter: store.tree.apberGqlFilterForTree, + apId, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.apberGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = data?.data?.apById?.apbersByApId?.nodes?.length ?? 0 + const totalCount = data?.data?.apById?.totalCount?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: 'AP-Berichte', + listFilter: 'apber', + url: `/Daten/Projekte/${projId}/Arten/${apId}/AP-Berichte`, + label: `AP-Berichte (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus: (data?.data?.apById?.apbersByApId?.nodes ?? []).map((p) => ({ + id: p.id, + label: p.label, + })), + }), + [ + apId, + count, + data?.data?.apById?.apbersByApId?.nodes, + isLoading, + projId, + totalCount, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useApberuebersichtsNavData.js b/src/modules/useApberuebersichtsNavData.js new file mode 100644 index 0000000000..3b2b98bcce --- /dev/null +++ b/src/modules/useApberuebersichtsNavData.js @@ -0,0 +1,84 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { useParams } from 'react-router' +import { reaction } from 'mobx' + +import { MobxContext } from '../mobxContext.js' + +export const useApberuebersichtsNavData = (props) => { + const apolloClient = useApolloClient() + const store = useContext(MobxContext) + + const params = useParams() + const projId = props?.projId ?? params?.projId + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treeApberuebersicht', projId], + queryFn: () => + apolloClient.query({ + query: gql` + query NavApberuebersichtsQuery( + $projId: UUID! + $apberuebersichtFilter: ApberuebersichtFilter! + ) { + projektById(id: $projId) { + id + apberuebersichtsByProjId(filter: $apberuebersichtFilter) { + totalCount + nodes { + id + label + } + } + allApberuebersichts: apberuebersichtsByProjId { + totalCount + } + } + } + `, + variables: { + projId, + apberuebersichtFilter: store.tree.apberuebersichtGqlFilterForTree, + }, + fetchPolicy: 'no-cache', + }), + }) + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.apberuebersichtGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = + data?.data?.projektById?.apberuebersichtsByProjId?.nodes?.length ?? 0 + const totalCount = + data?.data?.projektById?.allApberuebersichts?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: 'AP-Berichte', + listFilter: 'apberuebersicht', + url: `/Daten/Projekte/${projId}/AP-Berichte`, + label: 'AP-Berichte ' + (isLoading ? '...' : `${count}/${totalCount}`), + // TODO: needed? + totalCount, + menus: ( + data?.data?.projektById?.apberuebersichtsByProjId?.nodes ?? [] + ).map((p) => ({ + id: p.id, + label: p.label, + })), + }), + [ + count, + data?.data?.projektById?.apberuebersichtsByProjId?.nodes, + isLoading, + projId, + totalCount, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useApsNavData.js b/src/modules/useApsNavData.js new file mode 100644 index 0000000000..2319a62d8e --- /dev/null +++ b/src/modules/useApsNavData.js @@ -0,0 +1,66 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' +import { useParams } from 'react-router' + +import { MobxContext } from '../mobxContext.js' +export const useApsNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treeAp', projId, store.tree.apGqlFilterForTree], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeApsQuery($apsFilter: ApFilter!, $projId: UUID!) { + allAps(filter: $apsFilter, orderBy: LABEL_ASC) { + nodes { + id + label + } + } + totalCount: allAps(filter: { projId: { equalTo: $projId } }) { + totalCount + } + } + `, + variables: { + apsFilter: store.tree.apGqlFilterForTree, + projId, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.apGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = data?.data?.allAps?.nodes?.length ?? 0 + const totalCount = data?.data?.totalCount?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: 'Arten', + listFilter: 'ap', + url: `/Daten/Projekte/${projId}/Arten`, + label: `Arten (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus: (data?.data?.allAps?.nodes ?? [])?.map((p) => ({ + id: p.id, + label: p.label, + })), + }), + [count, data?.data?.allAps?.nodes, isLoading, projId, totalCount], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useAssozartsNavData.js b/src/modules/useAssozartsNavData.js new file mode 100644 index 0000000000..1795170aee --- /dev/null +++ b/src/modules/useAssozartsNavData.js @@ -0,0 +1,87 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' +import { useParams } from 'react-router' + +import { MobxContext } from '../mobxContext.js' + +export const useAssozartsNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: [ + 'treeAssozart', + projId, + apId, + store.tree.assozartGqlFilterForTree, + ], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeAssozartsQuery( + $assozartsFilter: AssozartFilter! + $apId: UUID! + ) { + apById(id: $apId) { + id + assozartsByApId(filter: $assozartsFilter, orderBy: LABEL_ASC) { + totalCount + nodes { + id + label + } + } + totalCount: assozartsByApId { + totalCount + } + } + } + `, + variables: { + assozartsFilter: store.tree.assozartGqlFilterForTree, + apId, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.assozartGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = data?.data?.apById?.assozartsByApId?.nodes?.length ?? 0 + const totalCount = data?.data?.apById?.totalCount?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: 'assoziierte-Arten', + listFilter: 'assozart', + url: `/Daten/Projekte/${projId}/Arten/${apId}/assoziierte-Arten`, + label: `Assoziierte Arten (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus: (data?.data?.apById?.assozartsByApId?.nodes ?? []).map((p) => ({ + id: p.id, + label: p.label, + })), + }), + [ + apId, + count, + data?.data?.apById?.assozartsByApId?.nodes, + isLoading, + projId, + totalCount, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useBeobNichtBeurteiltsNavData.js b/src/modules/useBeobNichtBeurteiltsNavData.js new file mode 100644 index 0000000000..870f4630b5 --- /dev/null +++ b/src/modules/useBeobNichtBeurteiltsNavData.js @@ -0,0 +1,140 @@ +import { useMemo, useContext, useEffect, useState, useCallback } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { useParams } from 'react-router' +import { reaction } from 'mobx' + +import { MobxContext } from '../mobxContext.js' +import { BeobnichtbeurteiltFilteredMapIcon } from '../components/NavElements/BeobnichtbeurteiltFilteredMapIcon.jsx' +import { useProjekteTabs } from './useProjekteTabs.js' + +export const useBeobNichtBeurteiltsNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const beobId = props?.beobId ?? params.beobId + + const [projekteTabs] = useProjekteTabs() + const karteIsVisible = projekteTabs.includes('karte') + + const store = useContext(MobxContext) + + const showBeobnichtbeurteiltIcon = + store.activeApfloraLayers?.includes('beobNichtBeurteilt') && karteIsVisible + const [, setRerenderer] = useState(0) + const rerender = useCallback(() => setRerenderer((prev) => prev + 1), []) + + const allBeobNichtBeurteiltFilter = useMemo( + () => ({ + tpopId: { isNull: true }, + nichtZuordnen: { equalTo: false }, + aeTaxonomyByArtId: { + apartsByArtId: { + some: { + apByApId: { + id: { equalTo: apId }, + }, + }, + }, + }, + }), + [apId], + ) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: [ + 'treeBeobNichtBeurteilts', + apId, + store.tree.beobNichtBeurteiltGqlFilterForTree, + ], + queryFn: () => + apolloClient.query({ + query: gql` + query NavBeobNichtBeurteiltsQuery( + $beobNichtBeurteiltFilter: BeobFilter! + $allBeobNichtBeurteiltFilter: BeobFilter! + ) { + beobsNichtBeurteilt: allBeobs( + filter: $allBeobNichtBeurteiltFilter + ) { + totalCount + } + filteredBeobsNichtBeurteilt: allBeobs( + filter: $beobNichtBeurteiltFilter + orderBy: [DATUM_DESC, AUTOR_ASC] + ) { + totalCount + nodes { + id + label + } + } + } + `, + variables: { + beobNichtBeurteiltFilter: { + ...store.tree.beobNichtBeurteiltGqlFilterForTree, + aeTaxonomyByArtId: { + apartsByArtId: { + some: { + apByApId: { + id: { equalTo: apId }, + }, + }, + }, + }, + }, + allBeobNichtBeurteiltFilter, + }, + fetchPolicy: 'no-cache', + }), + }) + useEffect( + () => + reaction(() => store.tree.beobNichtBeurteiltGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.activeApfloraLayers.slice(), rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = data?.data?.beobsNichtBeurteilt?.totalCount ?? 0 + const filteredCount = data?.data?.filteredBeobsNichtBeurteilt?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: 'nicht-beurteilte-Beobachtungen', + listFilter: 'beobNichtBeurteilt', + url: `/Daten/Projekte/${projId}/Arten/${apId}/nicht-beurteilte-Beobachtungen`, + label: `Beobachtungen nicht beurteilt (${isLoading ? '...' : `${filteredCount}/${count}`})`, + labelShort: `Beob. nicht beurteilt (${isLoading ? '...' : `${filteredCount}/${count}`})`, + // leave totalCount undefined as the menus are folders + menus: (data?.data?.filteredBeobsNichtBeurteilt?.nodes ?? []).map( + (p) => ({ + id: p.id, + label: p.label, + labelLeftElements: + showBeobnichtbeurteiltIcon && beobId === p.id ? + [BeobnichtbeurteiltFilteredMapIcon] + : undefined, + }), + ), + }), + [ + apId, + beobId, + count, + data?.data?.filteredBeobsNichtBeurteilt?.nodes, + filteredCount, + isLoading, + projId, + showBeobnichtbeurteiltIcon, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useBeobNichtZuzuordnensNavData.js b/src/modules/useBeobNichtZuzuordnensNavData.js new file mode 100644 index 0000000000..377bb98724 --- /dev/null +++ b/src/modules/useBeobNichtZuzuordnensNavData.js @@ -0,0 +1,140 @@ +import { useMemo, useContext, useEffect, useState, useCallback } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { useParams } from 'react-router' +import { reaction } from 'mobx' + +import { MobxContext } from '../mobxContext.js' +import { BeobnichtzuzuordnenFilteredMapIcon } from '../components/NavElements/BeobnichtzuzuordnenFilteredMapIcon.jsx' +import { useProjekteTabs } from './useProjekteTabs.js' + +export const useBeobNichtZuzuordnensNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const beobId = props?.beobId ?? params.beobId + + const [projekteTabs] = useProjekteTabs() + const karteIsVisible = projekteTabs.includes('karte') + + const store = useContext(MobxContext) + + const showBeobnichtzuzuordnenIcon = + store.activeApfloraLayers?.includes('beobNichtZuzuordnen') && karteIsVisible + const [, setRerenderer] = useState(0) + const rerender = useCallback(() => setRerenderer((prev) => prev + 1), []) + + const allBeobNichtZuzuordnenFilter = useMemo( + () => ({ + nichtZuordnen: { equalTo: true }, + aeTaxonomyByArtId: { + apartsByArtId: { + some: { + apByApId: { + id: { equalTo: apId }, + }, + }, + }, + }, + }), + [apId], + ) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: [ + 'treeBeobNichtZuzuordnens', + apId, + store.tree.beobNichtZuzuordnenGqlFilterForTree, + ], + queryFn: () => + apolloClient.query({ + query: gql` + query NavBeobNichtZuzuordnensQuery( + $beobNichtZuzuordnenFilter: BeobFilter! + $allBeobNichtZuzuordnenFilter: BeobFilter! + ) { + beobsNichtZuzuordnen: allBeobs( + filter: $allBeobNichtZuzuordnenFilter + ) { + totalCount + } + filteredBeobsNichtZuzuordnen: allBeobs( + filter: $beobNichtZuzuordnenFilter + orderBy: [DATUM_DESC, AUTOR_ASC] + ) { + totalCount + nodes { + id + label + } + } + } + `, + variables: { + beobNichtZuzuordnenFilter: { + ...store.tree.beobNichtZuzuordnenGqlFilterForTree, + aeTaxonomyByArtId: { + apartsByArtId: { + some: { + apByApId: { + id: { equalTo: apId }, + }, + }, + }, + }, + }, + allBeobNichtZuzuordnenFilter, + }, + fetchPolicy: 'no-cache', + }), + }) + useEffect( + () => + reaction(() => store.tree.beobNichtZuzuordnenGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.activeApfloraLayers.slice(), rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = data?.data?.beobsNichtZuzuordnen?.totalCount ?? 0 + const filteredCount = + data?.data?.filteredBeobsNichtZuzuordnen?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: 'nicht-zuzuordnende-Beobachtungen', + listFilter: 'beobNichtZuzuordnen', + url: `/Daten/Projekte/${projId}/Arten/${apId}/nicht-zuzuordnende-Beobachtungen`, + label: `Beobachtungen nicht zuzuordnen (${isLoading ? '...' : `${filteredCount}/${count}`})`, + labelShort: `Beob. nicht zuzuordnen (${isLoading ? '...' : `${filteredCount}/${count}`})`, + // leave totalCount undefined as the menus are folders + menus: (data?.data?.filteredBeobsNichtZuzuordnen?.nodes ?? []).map( + (p) => ({ + id: p.id, + label: p.label, + labelLeftElements: + showBeobnichtzuzuordnenIcon && beobId === p.id ? + [BeobnichtzuzuordnenFilteredMapIcon] + : undefined, + }), + ), + }), + [ + apId, + beobId, + count, + data?.data?.filteredBeobsNichtZuzuordnen?.nodes, + filteredCount, + isLoading, + projId, + showBeobnichtzuzuordnenIcon, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useBeobZugeordnetsNavData.js b/src/modules/useBeobZugeordnetsNavData.js new file mode 100644 index 0000000000..7f1500ed0a --- /dev/null +++ b/src/modules/useBeobZugeordnetsNavData.js @@ -0,0 +1,114 @@ +import { useMemo, useContext, useEffect, useState, useCallback } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { useParams } from 'react-router' +import { reaction } from 'mobx' + +import { MobxContext } from '../mobxContext.js' +import { BeobzugeordnetFilteredMapIcon } from '../components/NavElements/BeobzugeordnetFilteredMapIcon.jsx' +import { useProjekteTabs } from './useProjekteTabs.js' + +export const useBeobZugeordnetsNavData = (props) => { + const apolloClient = useApolloClient() + const store = useContext(MobxContext) + + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const popId = props?.popId ?? params.popId + const tpopId = props?.tpopId ?? params.tpopId + const beobId = props?.beobId ?? params.beobId + + const [projekteTabs] = useProjekteTabs() + const karteIsVisible = projekteTabs.includes('karte') + + const showBeobzugeordnetIcon = + store.activeApfloraLayers?.includes('beobZugeordnet') && karteIsVisible + const [, setRerenderer] = useState(0) + const rerender = useCallback(() => setRerenderer((prev) => prev + 1), []) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: [ + 'treeBeobZugeordnets', + apId, + store.tree.beobZugeordnetGqlFilterForTree, + ], + queryFn: () => + apolloClient.query({ + query: gql` + query NavBeobZugeordnetsQuery( + $beobZugeordnetFilter: BeobFilter! + $allBeobZugeordnetFilter: BeobFilter! + ) { + beobsZugeordnet: allBeobs(filter: $allBeobZugeordnetFilter) { + totalCount + } + filteredBeobsZugeordnet: allBeobs( + filter: $beobZugeordnetFilter + orderBy: [DATUM_DESC, AUTOR_ASC] + ) { + totalCount + nodes { + id + label + } + } + } + `, + variables: { + beobZugeordnetFilter: { + ...store.tree.beobZugeordnetGqlFilterForTree, + tpopId: { equalTo: tpopId }, + }, + allBeobZugeordnetFilter: { tpopId: { equalTo: tpopId } }, + }, + fetchPolicy: 'no-cache', + }), + }) + useEffect( + () => reaction(() => store.tree.beobZugeordnetGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.activeApfloraLayers.slice(), rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = data?.data?.beobsZugeordnet?.totalCount ?? 0 + const filteredCount = data?.data?.filteredBeobsZugeordnet?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: 'Beobachtungen', + listFilter: 'beobZugeordnet', + url: `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Teil-Populationen/${tpopId}/Beobachtungen`, + label: `Beobachtungen zugeordnet (${isLoading ? '...' : `${filteredCount}/${count}`})`, + labelShort: `Beob. zugeordnet (${isLoading ? '...' : `${filteredCount}/${count}`})`, + // leave totalCount undefined as the menus are folders + menus: (data?.data?.filteredBeobsZugeordnet?.nodes ?? []).map((p) => ({ + id: p.id, + label: p.label, + labelLeftElements: + showBeobzugeordnetIcon && beobId === p.id ? + [BeobzugeordnetFilteredMapIcon] + : undefined, + })), + }), + [ + apId, + beobId, + count, + data?.data?.filteredBeobsZugeordnet?.nodes, + filteredCount, + isLoading, + popId, + projId, + showBeobzugeordnetIcon, + tpopId, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useCurrentissuesNavData.js b/src/modules/useCurrentissuesNavData.js new file mode 100644 index 0000000000..edb022028f --- /dev/null +++ b/src/modules/useCurrentissuesNavData.js @@ -0,0 +1,50 @@ +import { useMemo } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' + +export const useCurrentissuesNavData = () => { + const apolloClient = useApolloClient() + + const { data, isLoading, error } = useQuery({ + queryKey: ['treeCurrentissues'], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeCurrentissuesQuery { + allCurrentissues(orderBy: [SORT_ASC, TITLE_ASC]) { + totalCount + nodes { + id + label + } + } + } + `, + fetchPolicy: 'no-cache', + }), + }) + + // subtract 1 for "fehlt hier was" + const count = (data?.data?.allCurrentissues?.nodes?.length ?? 1) - 1 + + const navData = useMemo( + () => ({ + id: 'AktuelleFehler', + url: `/Daten/Aktuelle-Fehler`, + label: `Aktuelle Fehler (${isLoading ? '...' : count})`, + totalCount: data?.data?.allCurrentissues?.totalCount ?? 0, + menus: (data?.data?.allCurrentissues?.nodes ?? []).map((p) => ({ + id: p.id, + label: p.label, + })), + }), + [ + count, + data?.data?.allCurrentissues?.nodes, + data?.data?.allCurrentissues?.totalCount, + isLoading, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useDocsNavData.js b/src/modules/useDocsNavData.js new file mode 100644 index 0000000000..5fbc4d3f1a --- /dev/null +++ b/src/modules/useDocsNavData.js @@ -0,0 +1,157 @@ +import { useMemo } from 'react' + +export const useDocsNavData = () => { + const navData = useMemo( + () => ({ + id: 'Dokumentation', + url: `/Dokumentation`, + label: 'Dokumentation', + // leave totalCount undefined as the menus are folders + menus: [ + { + id: 'was-kann-man-mit-apflora-machen', + label: `Was kann man mit apflora.ch machen?`, + }, + { + id: 'technische-voraussetzungen', + label: `Technische Voraussetzungen`, + }, + { + id: 'tipps-fuer-den-einstieg', + label: `Tipps für den Einstieg`, + }, + { + id: 'videos-fuer-den-einstieg', + label: `Videos für den Einstieg`, + }, + { + id: 'anleitung-eingabe', + label: `Anleitung zur Eingabe (inhaltlich), topos`, + }, + { + id: 'ist-apflora-langsam', + label: `Ist apflora langsam?`, + }, + { + id: 'art-auswertung-pop-menge', + label: `Art: Auswertung Population Mengen`, + }, + { + id: 'beobachtungen-einer-teil-population-zuordnen', + label: `Beobachtungen Teil-Populationen zuordnen`, + }, + { + id: 'falsch-bestimmte-beobachtungen', + label: `Falsch bestimmte Beobachtungen`, + }, + { + id: 'erfolgs-kontrollen-planen', + label: `Erfolgs-Kontrollen planen`, + }, + { + id: 'benutzer-konti', + label: `Benutzer-Konti`, + }, + { + id: 'erfolgs-kontrollen-freiwillige', + label: `Erfolgs-Kontrollen Freiwillige (EKF)`, + }, + { + id: 'filter', + label: `Filter`, + }, + { + id: 'markdown', + label: `Formatierbare Felder`, + }, + { + id: 'historisierung', + label: `Historisierung`, + }, + { + id: 'karte-teil-populationen-aller-arten-anzeigen', + label: `Karte: (Teil-)Populationen aller Arten anzeigen`, + }, + { + id: 'karte-filter', + label: `Karte: Filter`, + }, + { + id: 'karte-symbole-und-label-fuer-populationen-und-teil-populationen-waehlen', + label: `Karte: Symbole und Label für (Teil-)Populationen wählen`, + }, + { + id: 'karte-massstab', + label: `Karte: Massstab`, + }, + { + id: 'karte-drucken', + label: `Karte: Drucken`, + }, + { + id: 'gedaechtnis', + label: `apflora erinnert sich an euch`, + }, + { + id: 'dateien', + label: `Dateien anfügen`, + }, + { + id: 'koordinaten', + label: `Koordinaten`, + }, + { + id: 'melden', + label: `Fehler, Ideen, Vorschläge melden`, + }, + { + id: 'pwa', + label: `Progressive Web App`, + }, + { + id: 'technologien', + label: `Technologien`, + }, + { + id: 'beobachtungen-verwalten', + label: `Beobachtungen verwalten`, + }, + { + id: 'produkte-fuer-die-fns', + label: `Produkte für die Fachstelle Naturschutz`, + }, + { + id: 'daten-sichern', + label: `Daten sichern`, + }, + { + id: 'daten-wiederherstellen', + label: `Daten aus Sicherung herstellen`, + }, + { + id: 'testen', + label: `Funktionalität testen`, + }, + { + id: 'geschichte', + label: `Entstehungs-Geschichte`, + }, + { + id: 'open-source', + label: `Open source`, + }, + { + id: 'art-taxonomien-ergaenzen', + label: `Art-Taxonomien ergänzen`, + }, + { + id: 'info-flora-export', + label: `Info-Flora-Export`, + }, + ], + }), + [], + ) + + return { isLoading: false, error: undefined, navData } +} diff --git a/src/modules/useEkAbrechnungstypWertesNavData.js b/src/modules/useEkAbrechnungstypWertesNavData.js new file mode 100644 index 0000000000..99fe344274 --- /dev/null +++ b/src/modules/useEkAbrechnungstypWertesNavData.js @@ -0,0 +1,76 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' + +import { MobxContext } from '../mobxContext.js' + +export const useEkAbrechnungstypWertesNavData = () => { + const apolloClient = useApolloClient() + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: [ + 'treeEkAbrechnungstypWerte', + store.tree.ekAbrechnungstypWerteGqlFilterForTree, + ], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeEkAbrechnungstypWertesQuery( + $filter: EkAbrechnungstypWerteFilter! + ) { + allEkAbrechnungstypWertes( + filter: $filter + orderBy: [SORT_ASC, TEXT_ASC] + ) { + nodes { + id + label + } + } + totalCount: allEkAbrechnungstypWertes { + totalCount + } + } + `, + variables: { + filter: store.tree.ekAbrechnungstypWerteGqlFilterForTree, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => + reaction(() => store.tree.ekAbrechnungstypWerteGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + const count = data?.data?.allEkAbrechnungstypWertes?.nodes?.length ?? 0 + const totalCount = data?.data?.totalCount?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: 'EkAbrechnungstypWerte', + listFilter: 'ekAbrechnungstypWerte', + url: `/Daten/Werte-Listen/EkAbrechnungstypWerte`, + label: `Teil-Population: EK-Abrechnungstypen (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus: (data?.data?.allEkAbrechnungstypWertes?.nodes ?? []).map((p) => ({ + id: p.id, + label: p.label, + })), + }), + [ + count, + data?.data?.allEkAbrechnungstypWertes?.nodes, + isLoading, + totalCount, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useEkfrequenzsNavData.js b/src/modules/useEkfrequenzsNavData.js new file mode 100644 index 0000000000..74465925bd --- /dev/null +++ b/src/modules/useEkfrequenzsNavData.js @@ -0,0 +1,78 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' +import { useParams } from 'react-router' + +import { MobxContext } from '../mobxContext.js' + +export const useEkfrequenzsNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treeEkfrequenz', apId, store.tree.ekfrequenzGqlFilterForTree], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeEkfrequenzsQuery( + $ekfrequenzsFilter: EkfrequenzFilter! + $apId: UUID! + ) { + apById(id: $apId) { + id + ekfrequenzsByApId(filter: $ekfrequenzsFilter, orderBy: SORT_ASC) { + nodes { + id + label: code + } + } + totalCount: ekfrequenzsByApId { + totalCount + } + } + } + `, + variables: { + ekfrequenzsFilter: store.tree.ekfrequenzGqlFilterForTree, + apId, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.ekfrequenzGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const totalCount = data?.data?.apById?.totalCount?.totalCount ?? 0 + const menus = useMemo( + () => + (data?.data?.apById?.ekfrequenzsByApId?.nodes ?? []).map((p) => ({ + id: p.id, + label: p.label ?? '(kein Kürzel)', + })), + [data?.data?.apById?.ekfrequenzsByApId?.nodes], + ) + + const navData = useMemo( + () => ({ + id: 'EK-Frequenzen', + listFilter: 'ekfrequenz', + url: `/Daten/Projekte/${projId}/Arten/${apId}/EK-Frequenzen`, + label: `EK-Frequenzen (${isLoading ? '...' : `${menus.length}/${totalCount}`})`, + menus, + }), + [apId, isLoading, menus, projId, totalCount], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useEkzaehleinheitsNavData.js b/src/modules/useEkzaehleinheitsNavData.js new file mode 100644 index 0000000000..9a9e0aa896 --- /dev/null +++ b/src/modules/useEkzaehleinheitsNavData.js @@ -0,0 +1,90 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' +import { useParams } from 'react-router' + +import { MobxContext } from '../mobxContext.js' + +export const useEkzaehleinheitsNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: [ + 'treeEkzaehleinheit', + apId, + store.tree.ekzaehleinheitGqlFilterForTree, + ], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeEkzaehleinheitsQuery( + $ekzaehleinheitsFilter: EkzaehleinheitFilter! + $apId: UUID! + ) { + apById(id: $apId) { + id + ekzaehleinheitsByApId( + filter: $ekzaehleinheitsFilter + orderBy: [SORT_ASC, LABEL_ASC] + ) { + nodes { + id + label + } + } + totalCount: ekzaehleinheitsByApId { + totalCount + } + } + } + `, + variables: { + ekzaehleinheitsFilter: store.tree.ekzaehleinheitGqlFilterForTree, + apId, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.ekzaehleinheitGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const rows = useMemo( + () => data?.data?.apById?.ekzaehleinheitsByApId?.nodes ?? [], + [data], + ) + const count = rows.length + const totalCount = data?.data?.apById?.totalCount?.totalCount ?? 0 + const menus = useMemo( + () => + rows.map((p) => ({ + id: p.id, + label: p.label, + })), + [rows], + ) + + const navData = useMemo( + () => ({ + id: 'EK-Zähleinheiten', + listFilter: 'ekzaehleinheit', + url: `/Daten/Projekte/${projId}/Arten/${apId}/EK-Zähleinheiten`, + label: `EK-Zähleinheiten (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus, + }), + [apId, count, isLoading, menus, projId, totalCount], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useErfkritsNavData.js b/src/modules/useErfkritsNavData.js new file mode 100644 index 0000000000..1d978c27cd --- /dev/null +++ b/src/modules/useErfkritsNavData.js @@ -0,0 +1,85 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' +import { useParams } from 'react-router' + +import { MobxContext } from '../mobxContext.js' + +export const useErfkritsNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treeErfkrit', projId, apId, store.tree.erfkritGqlFilterForTree], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeErfkritsQuery( + $erfkritsFilter: ErfkritFilter! + $apId: UUID! + ) { + apById(id: $apId) { + id + erfkritsByApId( + filter: $erfkritsFilter + orderBy: AP_ERFKRIT_WERTE_BY_ERFOLG__SORT_ASC + ) { + totalCount + nodes { + id + label + } + } + totalCount: erfkritsByApId { + totalCount + } + } + } + `, + variables: { + erfkritsFilter: store.tree.erfkritGqlFilterForTree, + apId, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.erfkritGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = data?.data?.apById?.erfkritsByApId?.nodes?.length ?? 0 + const totalCount = data?.data?.apById?.totalCount?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: 'AP-Erfolgskriterien', + listFilter: 'erfkrit', + url: `/Daten/Projekte/${projId}/Arten/${apId}/AP-Erfolgskriterien`, + label: `AP-Erfolgskriterien (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus: (data?.data?.apById?.erfkritsByApId?.nodes ?? []).map((p) => ({ + id: p.id, + label: p.label, + })), + }), + [ + apId, + count, + data?.data?.apById?.erfkritsByApId?.nodes, + isLoading, + projId, + totalCount, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useFirstRender.js b/src/modules/useFirstRender.js new file mode 100644 index 0000000000..83f9faa624 --- /dev/null +++ b/src/modules/useFirstRender.js @@ -0,0 +1,9 @@ +import { useRef } from 'react' + +export const useFirstRender = () => { + const ref = useRef(true) + const firstRender = ref.current + ref.current = false + + return firstRender +} diff --git a/src/modules/useIdealbiotopNavData.js b/src/modules/useIdealbiotopNavData.js new file mode 100644 index 0000000000..96a0a58cdb --- /dev/null +++ b/src/modules/useIdealbiotopNavData.js @@ -0,0 +1,63 @@ +import { useMemo } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { useParams } from 'react-router' + +export const useIdealbiotopNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + + const { data, isLoading, error } = useQuery({ + queryKey: ['treeIdealbiotop', apId], + queryFn: () => + apolloClient.query({ + query: gql` + query NavIdealbiotopQuery($apId: UUID!) { + apById(id: $apId) { + id + label + idealbiotopsByApId { + nodes { + id + idealbiotopFilesByIdealbiotopId { + totalCount + } + } + } + } + } + `, + variables: { apId }, + fetchPolicy: 'no-cache', + }), + }) + + const idealbiotop = data?.data?.apById?.idealbiotopsByApId?.nodes?.[0] + const filesCount = + idealbiotop?.idealbiotopFilesByIdealbiotopId?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: apId, + url: `/Daten/Projekte/${projId}/Arten/${apId}/Idealbiotop`, + label: 'Idealbiotop', + // leave totalCount undefined as the menus are folders + menus: [ + { + id: 'Idealbiotop', + label: `Idealbiotop`, + }, + { + id: 'Dateien', + label: `Dateien (${filesCount})`, + count: filesCount, + }, + ], + }), + [filesCount, apId, projId], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useMessagesNavData.js b/src/modules/useMessagesNavData.js new file mode 100644 index 0000000000..9bf691fcde --- /dev/null +++ b/src/modules/useMessagesNavData.js @@ -0,0 +1,38 @@ +import { useMemo } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' + +export const useMessagesNavData = () => { + const apolloClient = useApolloClient() + + const { data, isLoading, error } = useQuery({ + queryKey: ['treeMessages'], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeMessagesQuery { + allMessages(orderBy: TIME_DESC) { + totalCount + nodes { + id + time + } + } + } + `, + fetchPolicy: 'no-cache', + }), + }) + + const navData = useMemo( + () => ({ + id: 'Mitteilungen', + url: `/Daten/Mitteilungen`, + label: `Mitteilungen (${isLoading ? '...' : data?.data?.allMessages?.totalCount})`, + // leave menus undefined as there are none + }), + [data?.data?.allMessages?.totalCount, isLoading], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/usePopNavData.js b/src/modules/usePopNavData.js new file mode 100644 index 0000000000..3d802ca988 --- /dev/null +++ b/src/modules/usePopNavData.js @@ -0,0 +1,236 @@ +import { useMemo, useContext, useEffect, useState, useCallback } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { useParams } from 'react-router' +import { reaction } from 'mobx' + +import { MobxContext } from '../mobxContext.js' +import { TpopMapIcon } from '../components/NavElements/TpopMapIcon.jsx' +import { useProjekteTabs } from './useProjekteTabs.js' +import { popIcons } from './usePopsNavData.js' +import { PopIconQHighlighted } from '../components/Projekte/Karte/layers/Pop/statusGroup/QHighlighted.jsx' +import { CopyingIcon } from '../components/NavElements/CopyingIcon.jsx' +import { MovingIcon } from '../components/NavElements/MovingIcon.jsx' + +export const usePopNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const popId = props?.popId ?? params.popId + + const [projekteTabs] = useProjekteTabs() + const karteIsVisible = projekteTabs.includes('karte') + + const store = useContext(MobxContext) + + const showTpopIcon = + store.activeApfloraLayers?.includes('tpop') && karteIsVisible + + const [, setRerenderer] = useState(0) + const rerender = useCallback(() => setRerenderer((prev) => prev + 1), []) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: [ + 'treePop', + popId, + store.tree.tpopGqlFilterForTree, + store.tree.popberGqlFilterForTree, + store.tree.popmassnberGqlFilterForTree, + ], + queryFn: () => + apolloClient.query({ + query: gql` + query NavPopQuery( + $popId: UUID! + $tpopFilter: TpopFilter! + $popberFilter: PopberFilter! + $popmassnberFilter: PopmassnberFilter! + ) { + popById(id: $popId) { + id + label + status + tpopsByPopId { + totalCount + } + filteredTpops: tpopsByPopId(filter: $tpopFilter) { + totalCount + } + popbersByPopId { + totalCount + } + filteredPopbers: popbersByPopId(filter: $popberFilter) { + totalCount + } + popmassnbersByPopId { + totalCount + } + filteredPopmassnbers: popmassnbersByPopId( + filter: $popmassnberFilter + ) { + totalCount + } + popFilesByPopId { + totalCount + } + } + allPopHistories(filter: { id: { equalTo: $popId } }) { + totalCount + } + } + `, + variables: { + popId, + tpopFilter: store.tree.tpopGqlFilterForTree, + popberFilter: store.tree.popberGqlFilterForTree, + popmassnberFilter: store.tree.popmassnberGqlFilterForTree, + }, + fetchPolicy: 'no-cache', + }), + }) + useEffect( + () => reaction(() => store.tree.tpopGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.tree.popberGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.tree.popmassnberGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.activeApfloraLayers.slice(), rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.map.popIcon, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.tree.showPopIcon, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.moving.id, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.copying.id, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const label = data?.data?.popById?.label + const status = data?.data?.popById?.status + const tpopsCount = data?.data?.popById?.tpopsByPopId?.totalCount ?? 0 + const filteredTpopsCount = data?.data?.popById?.filteredTpops?.totalCount ?? 0 + const popbersCount = data?.data?.popById?.popbersByPopId?.totalCount ?? 0 + const filteredPopbersCount = + data?.data?.popById?.filteredPopbers?.totalCount ?? 0 + const popmassnbersCount = + data?.data?.popById?.popmassnbersByPopId?.totalCount ?? 0 + const filteredPopmassnbersCount = + data?.data?.popById?.filteredPopmassnbers?.totalCount ?? 0 + const filesCount = data?.data?.popById?.popFilesByPopId?.totalCount ?? 0 + const historiesCount = data?.data?.allPopHistories?.totalCount ?? 0 + + const popIconName = store.map.popIcon + const PopIcon = + status ? popIcons[popIconName][status + 'Highlighted'] : PopIconQHighlighted + + const labelRightElements = useMemo(() => { + const labelRightElements = [] + const isMoving = store.moving.id === popId + if (isMoving) { + labelRightElements.push(MovingIcon) + } + const isCopying = store.copying.id === popId + if (isCopying) { + labelRightElements.push(CopyingIcon) + } + + return labelRightElements + }, [store.moving.id, store.copying.id, popId]) + + const navData = useMemo( + () => ({ + id: popId, + url: `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}`, + label, + labelLeftElements: store.tree.showPopIcon ? [PopIcon] : undefined, + labelRightElements: + labelRightElements.length ? labelRightElements : undefined, + // leave totalCount undefined as the menus are folders + menus: [ + { + id: 'Population', + label: `Population`, + labelLeftElements: store.tree.showPopIcon ? [PopIcon] : undefined, + labelRightElements: + labelRightElements.length ? labelRightElements : undefined, + }, + { + id: 'Teil-Populationen', + label: `Teil-Populationen (${isLoading ? '...' : `${filteredTpopsCount}/${tpopsCount}`})`, + count: tpopsCount, + labelLeftElements: showTpopIcon ? [TpopMapIcon] : undefined, + }, + { + id: 'Kontroll-Berichte', + label: `Kontroll-Berichte (${isLoading ? '...' : `${filteredPopbersCount}/${popbersCount}`})`, + count: popbersCount, + }, + { + id: 'Massnahmen-Berichte', + label: `Massnahmen-Berichte (${isLoading ? '...' : `${filteredPopmassnbersCount}/${popmassnbersCount}`})`, + count: popmassnbersCount, + }, + { + id: 'Auswertung', + label: `Auswertung`, + }, + { + id: 'Dateien', + label: `Dateien (${filesCount})`, + count: filesCount, + }, + { + id: 'Historien', + label: `Historien (${historiesCount})`, + count: historiesCount, + }, + ], + }), + [ + popId, + projId, + apId, + label, + store.tree.showPopIcon, + PopIcon, + labelRightElements, + isLoading, + filteredTpopsCount, + tpopsCount, + showTpopIcon, + filteredPopbersCount, + popbersCount, + filteredPopmassnbersCount, + popmassnbersCount, + filesCount, + historiesCount, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/usePopbersNavData.js b/src/modules/usePopbersNavData.js new file mode 100644 index 0000000000..8c8b4def60 --- /dev/null +++ b/src/modules/usePopbersNavData.js @@ -0,0 +1,77 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' +import { useParams } from 'react-router' + +import { MobxContext } from '../mobxContext.js' + +export const usePopbersNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const popId = props?.popId ?? params.popId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treePopber', popId, store.tree.popberGqlFilterForTree], + queryFn: () => + apolloClient.query({ + query: gql` + query TreePopbersQuery($popbersFilter: PopberFilter!, $popId: UUID!) { + popById(id: $popId) { + id + popbersByPopId(filter: $popbersFilter, orderBy: LABEL_ASC) { + nodes { + id + label + } + } + totalCount: popbersByPopId { + totalCount + } + } + } + `, + variables: { + popbersFilter: store.tree.popberGqlFilterForTree, + popId, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.popberGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = data?.data?.popById?.popbersByPopId?.nodes?.length ?? 0 + const totalCount = data?.data?.popById?.totalCount?.totalCount ?? 0 + const menus = useMemo( + () => + (data?.data?.popById?.popbersByPopId?.nodes ?? []).map((p) => ({ + id: p.id, + label: p.label, + })), + [data?.data?.popById?.popbersByPopId?.nodes], + ) + + const navData = useMemo( + () => ({ + id: 'Kontroll-Berichte', + listFilter: 'popber', + url: `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Kontroll-Berichte`, + label: `Kontroll-Berichte (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus, + }), + [apId, count, isLoading, menus, popId, projId, totalCount], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/usePopmassnbersNavData.js b/src/modules/usePopmassnbersNavData.js new file mode 100644 index 0000000000..01bea8fb3b --- /dev/null +++ b/src/modules/usePopmassnbersNavData.js @@ -0,0 +1,88 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' +import { useParams } from 'react-router' + +import { MobxContext } from '../mobxContext.js' + +export const usePopmassnbersNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const popId = props?.popId ?? params.popId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: [ + 'treePopmassnber', + popId, + store.tree.popmassnberGqlFilterForTree, + ], + queryFn: () => + apolloClient.query({ + query: gql` + query TreePopmassnbersQuery( + $popmassnbersFilter: PopmassnberFilter! + $popId: UUID! + ) { + popById(id: $popId) { + id + popmassnbersByPopId( + filter: $popmassnbersFilter + orderBy: LABEL_ASC + ) { + nodes { + id + label + } + } + totalCount: popmassnbersByPopId { + totalCount + } + } + } + `, + variables: { + popmassnbersFilter: store.tree.popmassnberGqlFilterForTree, + popId, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.popmassnberGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = data?.data?.popById?.popmassnbersByPopId?.nodes?.length ?? 0 + const totalCount = data?.data?.popById?.totalCount?.totalCount ?? 0 + const menus = useMemo( + () => + (data?.data?.popById?.popmassnbersByPopId?.nodes ?? []).map((p) => ({ + id: p.id, + label: p.label, + })), + [data?.data?.popById?.popmassnbersByPopId?.nodes], + ) + + const navData = useMemo( + () => ({ + id: 'Massnahmen-Berichte', + listFilter: 'popmassnber', + url: `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Massnahmen-Berichte`, + label: `Massnahmen-Berichte (${isLoading ? '...' : `${count}/${totalCount}`})`, + labelShort: `Massn.-Berichte (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus, + }), + [apId, count, isLoading, menus, popId, projId, totalCount], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/usePopsNavData.js b/src/modules/usePopsNavData.js new file mode 100644 index 0000000000..6685cbb30c --- /dev/null +++ b/src/modules/usePopsNavData.js @@ -0,0 +1,204 @@ +import { useMemo, useEffect, useContext, useState, useCallback } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' +import { useParams } from 'react-router' + +import { PopIcon100 } from '../components/Projekte/Karte/layers/Pop/statusGroupSymbols/100.jsx' +import { PopIcon100Highlighted } from '../components/Projekte/Karte/layers/Pop/statusGroupSymbols/100Highlighted.jsx' +import { PopIcon101 } from '../components/Projekte/Karte/layers/Pop/statusGroupSymbols/101.jsx' +import { PopIcon101Highlighted } from '../components/Projekte/Karte/layers/Pop/statusGroupSymbols/101Highlighted.jsx' +import { PopIcon200 } from '../components/Projekte/Karte/layers/Pop/statusGroupSymbols/200.jsx' +import { PopIcon200Highlighted } from '../components/Projekte/Karte/layers/Pop/statusGroupSymbols/200Highlighted.jsx' +import { PopIcon201 } from '../components/Projekte/Karte/layers/Pop/statusGroupSymbols/201.jsx' +import { PopIcon201Highlighted } from '../components/Projekte/Karte/layers/Pop/statusGroupSymbols/201Highlighted.jsx' +import { PopIcon202 } from '../components/Projekte/Karte/layers/Pop/statusGroupSymbols/202.jsx' +import { PopIcon202Highlighted } from '../components/Projekte/Karte/layers/Pop/statusGroupSymbols/202Highlighted.jsx' +import { PopIcon300 } from '../components/Projekte/Karte/layers/Pop/statusGroupSymbols/300.jsx' +import { PopIcon300Highlighted } from '../components/Projekte/Karte/layers/Pop/statusGroupSymbols/300Highlighted.jsx' +import { PopIcon } from '../components/Projekte/Karte/layers/Pop/Pop.jsx' +import { PopIconHighlighted } from '../components/Projekte/Karte/layers/Pop/PopHighlighted.jsx' +import { PopIconU } from '../components/Projekte/Karte/layers/Pop/statusGroup/U.jsx' +import { PopIconUHighlighted } from '../components/Projekte/Karte/layers/Pop/statusGroup/UHighlighted.jsx' +import { PopIconA } from '../components/Projekte/Karte/layers/Pop/statusGroup/A.jsx' +import { PopIconAHighlighted } from '../components/Projekte/Karte/layers/Pop/statusGroup/AHighlighted.jsx' +import { PopIconP } from '../components/Projekte/Karte/layers/Pop/statusGroup/P.jsx' +import { PopIconPHighlighted } from '../components/Projekte/Karte/layers/Pop/statusGroup/PHighlighted.jsx' +import { PopIconQ } from '../components/Projekte/Karte/layers/Pop/statusGroup/Q.jsx' +import { PopIconQHighlighted } from '../components/Projekte/Karte/layers/Pop/statusGroup/QHighlighted.jsx' + +import { MobxContext } from '../mobxContext.js' +import { CopyingIcon } from '../components/NavElements/CopyingIcon.jsx' +import { MovingIcon } from '../components/NavElements/MovingIcon.jsx' + +export const popIcons = { + normal: { + 100: PopIcon, + '100Highlighted': PopIconHighlighted, + 101: PopIcon, + '101Highlighted': PopIconHighlighted, + 200: PopIcon, + '200Highlighted': PopIconHighlighted, + 201: PopIcon, + '201Highlighted': PopIconHighlighted, + 202: PopIcon, + '202Highlighted': PopIconHighlighted, + 300: PopIcon, + '300Highlighted': PopIconHighlighted, + }, + statusGroup: { + 100: PopIconU, + '100Highlighted': PopIconUHighlighted, + 101: PopIconU, + '101Highlighted': PopIconUHighlighted, + 200: PopIconA, + '200Highlighted': PopIconAHighlighted, + 201: PopIconA, + '201Highlighted': PopIconAHighlighted, + 202: PopIconA, + '202Highlighted': PopIconAHighlighted, + 300: PopIconP, + '300Highlighted': PopIconPHighlighted, + }, + statusGroupSymbols: { + 100: PopIcon100, + '100Highlighted': PopIcon100Highlighted, + 101: PopIcon101, + '101Highlighted': PopIcon101Highlighted, + 200: PopIcon200, + '200Highlighted': PopIcon200Highlighted, + 201: PopIcon201, + '201Highlighted': PopIcon201Highlighted, + 202: PopIcon202, + '202Highlighted': PopIcon202Highlighted, + 300: PopIcon300, + '300Highlighted': PopIcon300Highlighted, + }, +} + +export const usePopsNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const popId = props?.popId ?? params.popId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treePop', projId, apId, store.tree.popGqlFilterForTree], + queryFn: () => + apolloClient.query({ + query: gql` + query TreePopsQuery($popsFilter: PopFilter!, $apId: UUID!) { + apById(id: $apId) { + id + popsByApId(filter: $popsFilter, orderBy: [NR_ASC, NAME_ASC]) { + totalCount + nodes { + id + label + status + } + } + totalCount: popsByApId { + totalCount + } + } + } + `, + variables: { + popsFilter: store.tree.popGqlFilterForTree, + apId, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.popGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + const [, setRerenderer] = useState(0) + const rerender = useCallback(() => setRerenderer((prev) => prev + 1), []) + useEffect( + () => reaction(() => store.map.popIcon, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.tree.showPopIcon, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.moving.id, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.copying.id, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = data?.data?.apById?.popsByApId?.nodes?.length ?? 0 + const totalCount = data?.data?.apById?.totalCount?.totalCount ?? 0 + + const popIconName = store.map.popIcon + + const navData = useMemo( + () => ({ + id: 'Populationen', + listFilter: 'pop', + url: `/Daten/Projekte/${projId}/Arten/${apId}/Populationen`, + label: `Populationen (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus: (data?.data?.apById?.popsByApId?.nodes ?? []).map((p) => { + const labelRightElements = [] + const isMoving = store.moving.id === p.id + if (isMoving) { + labelRightElements.push(MovingIcon) + } + const isCopying = store.copying.id === p.id + if (isCopying) { + labelRightElements.push(CopyingIcon) + } + + const popIconIsHighlighted = p.id === popId + const PopIcon = + p.status ? + popIconIsHighlighted ? + popIcons[popIconName][p.status + 'Highlighted'] + : popIcons[popIconName][p.status] + : popIconIsHighlighted ? PopIconQHighlighted + : PopIconQ + + return { + id: p.id, + label: p.label, + labelLeftElements: store.tree.showPopIcon ? [PopIcon] : undefined, + labelRightElements: + labelRightElements.length ? labelRightElements : undefined, + } + }), + }), + [ + apId, + count, + data?.data?.apById?.popsByApId?.nodes, + isLoading, + popIconName, + popId, + projId, + store.copying.id, + store.moving.id, + store.tree.showPopIcon, + totalCount, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/usePrevious.js b/src/modules/usePrevious.js new file mode 100644 index 0000000000..fb78002508 --- /dev/null +++ b/src/modules/usePrevious.js @@ -0,0 +1,9 @@ +import { useEffect, useRef } from 'react' + +export const usePrevious = (value) => { + const ref = useRef() + useEffect(() => { + ref.current = value //assign the value of ref to the argument + }, [value]) //this code will run when the value of 'value' changes + return ref.current //in the end, return the current ref value. +} diff --git a/src/modules/useProjektNavData.js b/src/modules/useProjektNavData.js new file mode 100644 index 0000000000..ec7b3bf2e9 --- /dev/null +++ b/src/modules/useProjektNavData.js @@ -0,0 +1,104 @@ +import { useMemo, useContext, useEffect } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { useParams } from 'react-router' +import { reaction } from 'mobx' + +import { MobxContext } from '../mobxContext.js' + +export const useProjektNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params?.projId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treeProject', projId], + queryFn: () => + apolloClient.query({ + query: gql` + query NavProjectQuery( + $projId: UUID! + $apFilter: ApFilter! + $apberuebersichtFilter: ApberuebersichtFilter! + ) { + projektById(id: $projId) { + id + label + apberuebersichtsByProjId(filter: $apberuebersichtFilter) { + totalCount + } + allApberuebersichts: apberuebersichtsByProjId { + totalCount + } + apsByProjId(filter: $apFilter) { + totalCount + } + allAps: apsByProjId { + totalCount + } + } + } + `, + variables: { + projId, + apFilter: store.tree.apGqlFilterForTree, + apberuebersichtFilter: store.tree.apberuebersichtGqlFilterForTree, + }, + fetchPolicy: 'no-cache', + }), + }) + useEffect( + () => reaction(() => store.tree.apGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.tree.apberuebersichtGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const label = data?.data?.projektById?.label ?? 'Projekt' + const artsCount = data?.data?.projektById?.apsByProjId?.totalCount ?? 0 + const allArtsCount = data?.data?.projektById?.allAps?.totalCount ?? 0 + const apberuebersichtsCount = + data?.data?.projektById?.apberuebersichtsByProjId?.totalCount ?? 0 + const allApberuebersichtsCount = + data?.data?.projektById?.allApberuebersichts?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: projId, + url: `/Daten/Projekte/${projId}`, + label, + // leave totalCount undefined as the menus are folders + menus: [ + { + id: 'Projekt', + label: 'Projekt', + }, + { + id: 'Arten', + label: `Arten (${isLoading ? '...' : `${artsCount}/${allArtsCount}`})`, + }, + { + id: 'AP-Berichte', + label: `AP-Berichte (${isLoading ? '...' : `${apberuebersichtsCount}/${allApberuebersichtsCount}`})`, + }, + ], + }), + [ + allApberuebersichtsCount, + allArtsCount, + apberuebersichtsCount, + artsCount, + isLoading, + label, + projId, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useProjekteNavData.js b/src/modules/useProjekteNavData.js new file mode 100644 index 0000000000..69fd2571d8 --- /dev/null +++ b/src/modules/useProjekteNavData.js @@ -0,0 +1,43 @@ +import { useMemo } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' + +export const useProjekteNavData = () => { + const apolloClient = useApolloClient() + + const { data, isLoading, error } = useQuery({ + queryKey: ['treeProjects'], + queryFn: () => + apolloClient.query({ + query: gql` + query NavProjectsQuery { + allProjekts { + totalCount + nodes { + id + name + } + } + } + `, + fetchPolicy: 'no-cache', + }), + }) + + const count = data?.data?.allProjekts?.nodes?.length ?? 0 + + const navData = useMemo( + () => ({ + id: 'projekte', + url: '/Daten/Projekte', + label: `Projekte (${isLoading ? '...' : count})`, + menus: (data?.data?.allProjekts?.nodes ?? []).map((p) => ({ + id: p.id, + label: p.name, + })), + }), + [count, data?.data?.allProjekts?.nodes, isLoading], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useProjekteTabs.js b/src/modules/useProjekteTabs.js new file mode 100644 index 0000000000..1c79c22686 --- /dev/null +++ b/src/modules/useProjekteTabs.js @@ -0,0 +1,21 @@ +import { useAtom } from 'jotai' + +import { useSearchParamsState } from './useSearchParamsState.js' +import { constants } from './constants.js' +import { alwaysShowTreeAtom } from '../JotaiStore/index.js' + +const isMobileView = window.innerWidth <= constants.mobileViewMaxWidth + +export const useProjekteTabs = () => { + const [alwaysShowTree] = useAtom(alwaysShowTreeAtom) + const showTree = alwaysShowTree || !isMobileView + const [projekteTabs, setProjekteTabs] = useSearchParamsState( + 'projekteTabs', + showTree ? + isMobileView ? ['tree'] + : ['tree', 'daten'] + : ['daten'], + ) + + return [projekteTabs, setProjekteTabs] +} diff --git a/src/modules/useRootNavData.js b/src/modules/useRootNavData.js new file mode 100644 index 0000000000..59d7755b35 --- /dev/null +++ b/src/modules/useRootNavData.js @@ -0,0 +1,93 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' + +import { MobxContext } from '../mobxContext.js' + +export const useRootNavData = () => { + const apolloClient = useApolloClient() + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treeRoot', store.tree.userGqlFilterForTree], + queryFn: () => + apolloClient.query({ + query: gql` + query NavRootQuery($usersFilter: UserFilter!) { + allProjekts { + totalCount + } + allUsers { + totalCount + } + filteredUsers: allUsers(filter: $usersFilter) { + totalCount + } + allMessages { + totalCount + } + allCurrentissues { + totalCount + } + } + `, + variables: { + usersFilter: store.tree.userGqlFilterForTree, + }, + fetchPolicy: 'no-cache', + }), + }) + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.userGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + const projectsCount = data?.data?.allProjekts?.totalCount ?? 0 + const usersCount = data?.data?.allUsers?.totalCount ?? 0 + const usersFilteredCount = data?.data?.filteredUsers?.totalCount ?? 0 + const messagesCount = data?.data?.allMessages?.totalCount ?? 0 + const currentIssuesCount = data?.data?.allCurrentissues?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: 'Daten', + url: '/Daten', + label: `Daten`, + // leave totalCount undefined as the menus are folders + menus: [ + { + id: 'Projekte/e57f56f4-4376-11e8-ab21-4314b6749d13', + label: `Projekte (${isLoading ? '...' : projectsCount})`, + }, + { + id: 'Benutzer', + label: `Benutzer (${isLoading ? '...' : `${usersFilteredCount}/${usersCount}`})`, + }, + { + id: 'Werte-Listen', + label: `Werte-Listen`, + }, + { + id: 'Mitteilungen', + label: `Mitteilungen (${isLoading ? '...' : messagesCount})`, + }, + { + id: 'Aktuelle-Fehler', + label: `Aktuelle Fehler (${isLoading ? '...' : currentIssuesCount})`, + }, + ], + }), + [ + isLoading, + projectsCount, + usersFilteredCount, + usersCount, + messagesCount, + currentIssuesCount, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useSearchParamsState.js b/src/modules/useSearchParamsState.js index 44036bd419..d208280f2b 100644 --- a/src/modules/useSearchParamsState.js +++ b/src/modules/useSearchParamsState.js @@ -15,14 +15,6 @@ export function useSearchParamsState(searchParamName, defaultValue) { searchParamsState = acquiredSearchParam ? acquiredSearchParam : defaultValue } - // console.log('useSearchParamsState', { - // acquiredSearchParam, - // searchParamsState, - // searchParamName, - // defaultValue, - // searchParamsEntries: [...searchParams.entries()], - // }) - const setSearchParamsState = (newState) => { const previous = [...searchParams.entries()].reduce((o, [key, value]) => { if (key in o) { diff --git a/src/modules/useTpopApberrelevantGrundWertesNavData.js b/src/modules/useTpopApberrelevantGrundWertesNavData.js new file mode 100644 index 0000000000..d4bb5b3748 --- /dev/null +++ b/src/modules/useTpopApberrelevantGrundWertesNavData.js @@ -0,0 +1,82 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' + +import { MobxContext } from '../mobxContext.js' + +export const useTpopApberrelevantGrundWertesNavData = () => { + const apolloClient = useApolloClient() + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: [ + 'treeTpopApberrelevantGrundWerte', + store.tree.tpopApberrelevantGrundWerteGqlFilterForTree, + ], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeTpopApberrelevantGrundWerteQuery( + $tpopApberrelevantGrundWertsFilter: TpopApberrelevantGrundWerteFilter! + ) { + allTpopApberrelevantGrundWertes( + filter: $tpopApberrelevantGrundWertsFilter + orderBy: [SORT_ASC, TEXT_ASC] + ) { + nodes { + id + label + } + } + totalCount: allTpopApberrelevantGrundWertes { + totalCount + } + } + `, + variables: { + tpopApberrelevantGrundWertsFilter: + store.tree.tpopApberrelevantGrundWerteGqlFilterForTree, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => + reaction( + () => store.tree.tpopApberrelevantGrundWerteGqlFilterForTree, + refetch, + ), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + const count = data?.data?.allTpopApberrelevantGrundWertes?.nodes?.length ?? 0 + const totalCount = data?.data?.totalCount?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: 'ApberrelevantGrundWerte', + listFilter: 'tpopApberrelevantGrundWerte', + url: `/Daten/Werte-Listen/ApberrelevantGrundWerte`, + label: `Teil-Population: Grund für AP-Bericht Relevanz (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus: (data?.data?.allTpopApberrelevantGrundWertes?.nodes ?? []).map( + (p) => ({ + id: p.id, + label: p.label, + }), + ), + }), + [ + count, + data?.data?.allTpopApberrelevantGrundWertes?.nodes, + isLoading, + totalCount, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useTpopNavData.js b/src/modules/useTpopNavData.js new file mode 100644 index 0000000000..c297f0f288 --- /dev/null +++ b/src/modules/useTpopNavData.js @@ -0,0 +1,327 @@ +import { useMemo, useContext, useEffect, useState, useCallback } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { useParams } from 'react-router' +import { reaction } from 'mobx' + +import { MobxContext } from '../mobxContext.js' +import { BeobzugeordnetMapIcon } from '../components/NavElements/BeobzugeordnetMapIcon.jsx' +import { useProjekteTabs } from './useProjekteTabs.js' + +// TODO:remove unused +import { TpopIconQHighlighted } from '../components/Projekte/Karte/layers/Tpop/statusGroup/qHighlighted.jsx' +import { tpopIcons } from './useTpopsNavData.js' +import { MovingIcon } from '../components/NavElements/MovingIcon.jsx' +import { CopyingIcon } from '../components/NavElements/CopyingIcon.jsx' + +export const useTpopNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const popId = props?.popId ?? params.popId + const tpopId = props?.tpopId ?? params.tpopId + + const [projekteTabs] = useProjekteTabs() + const karteIsVisible = projekteTabs.includes('karte') + + const store = useContext(MobxContext) + const showBeobzugeordnetIcon = + store.activeApfloraLayers?.includes('beobZugeordnet') && karteIsVisible + const [, setRerenderer] = useState(0) + const rerender = useCallback(() => setRerenderer((prev) => prev + 1), []) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: [ + 'treeTpop', + tpopId, + store.tree.tpopmassnGqlFilterForTree, + store.tree.tpopmassnberGqlFilterForTree, + store.tree.ekGqlFilterForTree, + store.tree.ekfGqlFilterForTree, + store.tree.tpopberGqlFilterForTree, + store.tree.beobZugeordnetGqlFilterForTree, + ], + queryFn: () => + apolloClient.query({ + query: gql` + query NavTpopQuery( + $tpopId: UUID! + $tpopmassnFilter: TpopmassnFilter! + $tpopmassnberFilter: TpopmassnberFilter! + $tpopfeldkontrFilter: TpopkontrFilter! + $tpopfreiwkontrFilter: TpopkontrFilter! + $tpopberFilter: TpopberFilter! + $beobZugeordnetFilter: BeobFilter! + ) { + tpopById(id: $tpopId) { + id + label + status + tpopmassnsByTpopId { + totalCount + } + filteredTpopmassns: tpopmassnsByTpopId(filter: $tpopmassnFilter) { + totalCount + } + tpopmassnbersByTpopId { + totalCount + } + filteredTpopmassnbers: tpopmassnbersByTpopId( + filter: $tpopmassnberFilter + ) { + totalCount + } + tpopfeldkontrs: tpopkontrsByTpopId( + filter: { + typ: { distinctFrom: "Freiwilligen-Erfolgskontrolle" } + } + ) { + totalCount + } + filteredTpopfeldkontrs: tpopkontrsByTpopId( + filter: $tpopfeldkontrFilter + ) { + totalCount + } + tpopfreiwkontrs: tpopkontrsByTpopId( + filter: { typ: { equalTo: "Freiwilligen-Erfolgskontrolle" } } + ) { + totalCount + } + filteredTpopfreiwkontrs: tpopkontrsByTpopId( + filter: $tpopfreiwkontrFilter + ) { + totalCount + } + tpopbersByTpopId { + totalCount + } + filteredTpopbers: tpopbersByTpopId(filter: $tpopberFilter) { + totalCount + } + beobZugeordnet: beobsByTpopId( + filter: { tpopId: { equalTo: $tpopId } } + ) { + totalCount + } + filteredBeobZugeordnet: beobsByTpopId( + filter: $beobZugeordnetFilter + ) { + totalCount + } + tpopFilesByTpopId { + totalCount + } + } + allTpopHistories(filter: { id: { equalTo: $tpopId } }) { + totalCount + } + } + `, + variables: { + tpopId, + tpopmassnFilter: store.tree.tpopmassnGqlFilterForTree, + tpopmassnberFilter: store.tree.tpopmassnberGqlFilterForTree, + tpopfeldkontrFilter: store.tree.ekGqlFilterForTree, + tpopfreiwkontrFilter: store.tree.ekfGqlFilterForTree, + tpopberFilter: store.tree.tpopberGqlFilterForTree, + beobZugeordnetFilter: { + ...store.tree.beobZugeordnetGqlFilterForTree, + tpopId: { equalTo: tpopId }, + }, + }, + fetchPolicy: 'no-cache', + }), + }) + useEffect( + () => reaction(() => store.tree.tpopmassnGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.tree.tpopmassnberGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.tree.ekGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.tree.ekfGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.tree.tpopberGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.tree.beobZugeordnetGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.activeApfloraLayers.slice(), rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.map.tpopIcon, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.tree.showTpopIcon, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.moving.id, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.copying.id, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const label = data?.data?.tpopById?.label + const status = data?.data?.tpopById?.status + const massnCount = data?.data?.tpopById?.tpopmassnsByTpopId?.totalCount ?? 0 + const filteredMassnCount = + data?.data?.tpopById?.filteredTpopmassns?.totalCount ?? 0 + const popmassnbersCount = + data?.data?.tpopById?.tpopmassnbersByTpopId?.totalCount ?? 0 + const filteredPopmassnbersCount = + data?.data?.tpopById?.filteredTpopmassnbers?.totalCount ?? 0 + const feldkontrCount = data?.data?.tpopById?.tpopfeldkontrs?.totalCount ?? 0 + const filteredFeldkontrCount = + data?.data?.tpopById?.filteredTpopfeldkontrs?.totalCount ?? 0 + const freiwkontrCount = data?.data?.tpopById?.tpopfreiwkontrs?.totalCount ?? 0 + const filteredFreiwkontrCount = + data?.data?.tpopById?.filteredTpopfreiwkontrs?.totalCount ?? 0 + const tpopbersCount = data?.data?.tpopById?.tpopbersByTpopId?.totalCount ?? 0 + const filteredTpopbersCount = + data?.data?.tpopById?.filteredTpopbers?.totalCount ?? 0 + const beobZugeordnetCount = + data?.data?.tpopById?.beobZugeordnet?.totalCount ?? 0 + const filteredBeobZugeordnetCount = + data?.data?.tpopById?.filteredBeobZugeordnet?.totalCount ?? 0 + const filesCount = data?.data?.tpopById?.tpopFilesByTpopId?.totalCount ?? 0 + const historiesCount = data?.data?.allTpopHistories?.totalCount ?? 0 + + const tpopIconName = store.map.tpopIcon + + const TpopIcon = + status ? + tpopIcons[tpopIconName][status + 'Highlighted'] + : TpopIconQHighlighted + + const labelRightElements = useMemo(() => { + const labelRightElements = [] + const isMoving = store.moving.id === tpopId + if (isMoving) { + labelRightElements.push(MovingIcon) + } + const isCopying = store.copying.id === tpopId + if (isCopying) { + labelRightElements.push(CopyingIcon) + } + + return labelRightElements + }, [store.moving.id, store.copying.id, tpopId]) + + const navData = useMemo( + () => ({ + id: tpopId, + url: `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Teil-Populationen/${tpopId}`, + label, + labelLeftElements: store.tree.showTpopIcon ? [TpopIcon] : undefined, + labelRightElements: + labelRightElements.length ? labelRightElements : undefined, + // leave totalCount undefined as the menus are folders + menus: [ + { + id: 'Teil-Population', + label: `Teil-Population`, + labelLeftElements: store.tree.showTpopIcon ? [TpopIcon] : undefined, + labelRightElements: + labelRightElements.length ? labelRightElements : undefined, + }, + { + id: 'Massnahmen', + label: `Massnahmen (${isLoading ? '...' : `${filteredMassnCount}/${massnCount}`})`, + }, + { + id: 'Massnahmen-Berichte', + label: `Massnahmen-Berichte (${isLoading ? '...' : `${filteredPopmassnbersCount}/${popmassnbersCount}`})`, + }, + { + id: 'Feld-Kontrollen', + label: `Feld-Kontrollen (${isLoading ? '...' : `${filteredFeldkontrCount}/${feldkontrCount}`})`, + }, + { + id: 'Freiwilligen-Kontrollen', + label: `Freiwilligen-Kontrollen (${isLoading ? '...' : `${filteredFreiwkontrCount}/${freiwkontrCount}`})`, + }, + { + id: 'Kontroll-Berichte', + label: `Kontroll-Berichte (${isLoading ? '...' : `${filteredTpopbersCount}/${tpopbersCount}`})`, + }, + { + id: 'Beobachtungen', + label: `Beobachtungen zugeordnet (${isLoading ? '...' : `${filteredBeobZugeordnetCount}/${beobZugeordnetCount}`})`, + labelLeftElements: + showBeobzugeordnetIcon ? [BeobzugeordnetMapIcon] : undefined, + }, + { + id: 'EK', + label: `EK`, + }, + { + id: 'Dateien', + label: `Dateien (${filesCount})`, + count: filesCount, + }, + { + id: 'Historien', + label: `Historien (${historiesCount})`, + count: historiesCount, + }, + ], + }), + [ + TpopIcon, + apId, + beobZugeordnetCount, + feldkontrCount, + filesCount, + filteredBeobZugeordnetCount, + filteredFeldkontrCount, + filteredFreiwkontrCount, + filteredMassnCount, + filteredPopmassnbersCount, + filteredTpopbersCount, + freiwkontrCount, + historiesCount, + isLoading, + label, + labelRightElements, + massnCount, + popId, + popmassnbersCount, + projId, + showBeobzugeordnetIcon, + store.tree.showTpopIcon, + tpopId, + tpopbersCount, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useTpopbersNavData.js b/src/modules/useTpopbersNavData.js new file mode 100644 index 0000000000..cdfe623908 --- /dev/null +++ b/src/modules/useTpopbersNavData.js @@ -0,0 +1,86 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' +import { useParams } from 'react-router' + +import { MobxContext } from '../mobxContext.js' + +export const useTpopbersNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const popId = props?.popId ?? params.popId + const tpopId = props?.tpopId ?? params.tpopId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treeTpopber', tpopId, store.tree.tpopberGqlFilterForTree], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeTpopbersQuery( + $tpopbersFilter: TpopberFilter! + $tpopId: UUID! + ) { + tpopById(id: $tpopId) { + id + tpopbersByTpopId(filter: $tpopbersFilter, orderBy: LABEL_ASC) { + totalCount + nodes { + id + label + } + } + totalCount: tpopbersByTpopId { + totalCount + } + } + } + `, + variables: { + tpopbersFilter: store.tree.tpopberGqlFilterForTree, + tpopId, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.tpopberGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = data?.data?.tpopById?.tpopbersByTpopId?.totalCount ?? 0 + const totalCount = data?.data?.tpopById?.totalCount?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: 'Kontroll-Berichte', + listFilter: 'tpopber', + url: `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Teil-Populationen/${tpopId}/Kontroll-Berichte`, + label: `Kontroll-Berichte (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus: (data?.data?.tpopById?.tpopbersByTpopId?.nodes ?? []).map((p) => ({ + id: p.id, + label: p.label, + })), + }), + [ + apId, + count, + data?.data?.tpopById?.tpopbersByTpopId?.nodes, + isLoading, + popId, + projId, + totalCount, + tpopId, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useTpopfeldkontrNavData.js b/src/modules/useTpopfeldkontrNavData.js new file mode 100644 index 0000000000..e76d341562 --- /dev/null +++ b/src/modules/useTpopfeldkontrNavData.js @@ -0,0 +1,156 @@ +import { useMemo, useContext, useEffect, useState, useCallback } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { useParams } from 'react-router' +import { reaction } from 'mobx' + +import { MobxContext } from '../mobxContext.js' + +import { MovingIcon } from '../components/NavElements/MovingIcon.jsx' +import { CopyingIcon } from '../components/NavElements/CopyingIcon.jsx' +import { BiotopCopyingIcon } from '../components/NavElements/BiotopCopyingIcon.jsx' + +export const useTpopfeldkontrNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const popId = props?.popId ?? params.popId + const tpopId = props?.tpopId ?? params.tpopId + const tpopkontrId = props?.tpopkontrId ?? params.tpopkontrId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: [ + 'treeTpopfeldkontr', + tpopkontrId, + store.tree.tpopkontrzaehlGqlFilterForTree, + ], + queryFn: () => + apolloClient.query({ + query: gql` + query NavTpopfeldkontrQuery( + $tpopkontrId: UUID! + $tpopkontrzaehlFilter: TpopkontrzaehlFilter! + ) { + tpopkontrById(id: $tpopkontrId) { + id + label: labelEk + tpopkontrzaehlsByTpopkontrId { + totalCount + } + tpopkontrFilesByTpopkontrId { + totalCount + } + filteredTpopkontrzaehls: tpopkontrzaehlsByTpopkontrId( + filter: $tpopkontrzaehlFilter + ) { + totalCount + } + } + } + `, + variables: { + tpopkontrId, + tpopkontrzaehlFilter: store.tree.tpopkontrzaehlGqlFilterForTree, + }, + fetchPolicy: 'no-cache', + }), + }) + useEffect( + () => reaction(() => store.tree.tpopkontrzaehlGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + const [, setRerenderer] = useState(0) + const rerender = useCallback(() => setRerenderer((prev) => prev + 1), []) + useEffect( + () => reaction(() => store.copyingBiotop, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.moving.id, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.copying.id, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const label = data?.data?.tpopkontrById?.label + const tpopkontrzaehlCount = + data?.data?.tpopkontrById?.tpopkontrzaehlsByTpopkontrId?.totalCount ?? 0 + const filteredTpopkontrzaehlCount = + data?.data?.tpopkontrById?.filteredTpopkontrzaehls?.totalCount ?? 0 + const filesCount = + data?.data?.tpopkontrById?.tpopkontrFilesByTpopkontrId?.totalCount ?? 0 + + const labelRightElements = useMemo(() => { + const labelRightElements = [] + const isMoving = store.moving.id === tpopkontrId + if (isMoving) { + labelRightElements.push(MovingIcon) + } + const isCopying = store.copying.id === tpopkontrId + if (isCopying) { + labelRightElements.push(CopyingIcon) + } + const isCopyingBiotop = store.copyingBiotop.id === tpopkontrId + if (isCopyingBiotop) { + labelRightElements.push(BiotopCopyingIcon) + } + + return labelRightElements + }, [store.copying.id, store.copyingBiotop.id, store.moving.id, tpopkontrId]) + + const navData = useMemo( + () => ({ + id: tpopkontrId, + url: `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Teil-Populationen/${tpopId}/Feld-Kontrollen/${tpopkontrId}`, + label, + labelRightElements: + labelRightElements.length ? labelRightElements : undefined, + // leave totalCount undefined as the menus are folders + menus: [ + { + id: 'Feld-Kontrolle', + label: `Feld-Kontrolle`, + labelRightElements: + labelRightElements.length ? labelRightElements : undefined, + }, + { + id: 'Zaehlungen', + label: `Zählungen (${isLoading ? '...' : `${filteredTpopkontrzaehlCount}/${tpopkontrzaehlCount}`})`, + }, + { + id: 'Biotop', + label: `Biotop`, + }, + { + id: 'Dateien', + label: `Dateien (${filesCount})`, + count: filesCount, + }, + ], + }), + [ + apId, + filesCount, + filteredTpopkontrzaehlCount, + isLoading, + label, + labelRightElements, + popId, + projId, + tpopId, + tpopkontrId, + tpopkontrzaehlCount, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useTpopfeldkontrsNavData.js b/src/modules/useTpopfeldkontrsNavData.js new file mode 100644 index 0000000000..448e5638c2 --- /dev/null +++ b/src/modules/useTpopfeldkontrsNavData.js @@ -0,0 +1,134 @@ +import { useMemo, useEffect, useContext, useState, useCallback } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' +import { useParams } from 'react-router' + +import { MobxContext } from '../mobxContext.js' + +import { MovingIcon } from '../components/NavElements/MovingIcon.jsx' +import { CopyingIcon } from '../components/NavElements/CopyingIcon.jsx' +import { BiotopCopyingIcon } from '../components/NavElements/BiotopCopyingIcon.jsx' + +export const useTpopfeldkontrsNavData = (props) => { + const apolloClient = useApolloClient() + + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const popId = props?.popId ?? params.popId + const tpopId = props?.tpopId ?? params.tpopId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treeTpopfeldkontr', tpopId, store.tree.ekGqlFilterForTree], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeTpopfeldkontrsQuery( + $eksFilter: TpopkontrFilter! + $tpopId: UUID! + ) { + tpopById(id: $tpopId) { + id + tpopkontrsByTpopId( + filter: $eksFilter + orderBy: [JAHR_ASC, DATUM_ASC] + ) { + totalCount + nodes { + id + label: labelEk + } + } + totalCount: tpopkontrsByTpopId( + filter: { + typ: { distinctFrom: "Freiwilligen-Erfolgskontrolle" } + } + ) { + totalCount + } + } + } + `, + variables: { + eksFilter: store.tree.ekGqlFilterForTree, + tpopId, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.ekGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + const [, setRerenderer] = useState(0) + const rerender = useCallback(() => setRerenderer((prev) => prev + 1), []) + useEffect( + () => reaction(() => store.moving.id, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.copying.id, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = data?.data?.tpopById?.tpopkontrsByTpopId?.totalCount ?? 0 + const totalCount = data?.data?.tpopById?.totalCount?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: 'Feld-Kontrollen', + listFilter: 'tpopkontr', + url: `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Teil-Populationen/${tpopId}/Feld-Kontrollen`, + label: `Feld-Kontrollen (${isLoading ? '...' : `${count}/${totalCount}`})`, + labelShort: `EK (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus: (data?.data?.tpopById?.tpopkontrsByTpopId?.nodes ?? []).map( + (p) => { + const labelRightElements = [] + const isMoving = store.moving.id === p.id + if (isMoving) { + labelRightElements.push(MovingIcon) + } + const isCopying = store.copying.id === p.id + if (isCopying) { + labelRightElements.push(CopyingIcon) + } + const isCopyingBiotop = store.copyingBiotop.id === p.id + if (isCopyingBiotop) { + labelRightElements.push(BiotopCopyingIcon) + } + + return { + id: p.id, + label: p.label, + labelRightElements: + labelRightElements.length ? labelRightElements : undefined, + } + }, + ), + }), + [ + apId, + count, + data?.data?.tpopById?.tpopkontrsByTpopId?.nodes, + isLoading, + popId, + projId, + store.copying.id, + store.copyingBiotop.id, + store.moving.id, + totalCount, + tpopId, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useTpopfeldkontrzaehlsNavData.js b/src/modules/useTpopfeldkontrzaehlsNavData.js new file mode 100644 index 0000000000..f0d5a01c73 --- /dev/null +++ b/src/modules/useTpopfeldkontrzaehlsNavData.js @@ -0,0 +1,103 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' +import { useParams } from 'react-router' + +import { MobxContext } from '../mobxContext.js' + +export const useTpopfeldkontrzaehlsNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const popId = props?.popId ?? params.popId + const tpopId = props?.tpopId ?? params.tpopId + const tpopkontrId = props?.tpopkontrId ?? params.tpopkontrId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: [ + 'treeTpopfeldkontrzaehl', + tpopkontrId, + store.tree.tpopkontrzaehlGqlFilterForTree, + ], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeTpopfeldkontrzaehlsQuery( + $tpopkontrzaehlsFilter: TpopkontrzaehlFilter! + $tpopkontrId: UUID! + ) { + tpopkontrById(id: $tpopkontrId) { + id + tpopkontrzaehlsByTpopkontrId( + filter: $tpopkontrzaehlsFilter + orderBy: LABEL_ASC + ) { + totalCount + nodes { + id + label + } + } + totalCount: tpopkontrzaehlsByTpopkontrId { + totalCount + } + } + } + `, + variables: { + tpopkontrzaehlsFilter: store.tree.tpopkontrzaehlGqlFilterForTree, + tpopkontrId, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.tpopkontrzaehlGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = + data?.data?.tpopkontrById?.tpopkontrzaehlsByTpopkontrId?.totalCount ?? 0 + const totalCount = data?.data?.tpopkontrById?.totalCount?.totalCount ?? 0 + const menus = useMemo( + () => + data?.data?.tpopkontrById?.tpopkontrzaehlsByTpopkontrId?.nodes?.map( + (p) => ({ + id: p.id, + label: p.label, + }), + ) ?? [], + [data?.data?.tpopkontrById?.tpopkontrzaehlsByTpopkontrId?.nodes], + ) + + const navData = useMemo( + () => ({ + id: 'Zaehlungen', + listFilter: 'tpopkontrzaehl', + url: `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Teil-Populationen/${tpopId}/Feld-Kontrollen/${tpopkontrId}/Zaehlungen`, + label: `Zählungen (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus, + }), + [ + apId, + count, + isLoading, + menus, + popId, + projId, + totalCount, + tpopId, + tpopkontrId, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useTpopfreiwkontrNavData.js b/src/modules/useTpopfreiwkontrNavData.js new file mode 100644 index 0000000000..c4866f517d --- /dev/null +++ b/src/modules/useTpopfreiwkontrNavData.js @@ -0,0 +1,142 @@ +import { useMemo, useContext, useEffect, useState, useCallback } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { useParams } from 'react-router' +import { reaction } from 'mobx' + +import { MobxContext } from '../mobxContext.js' +import { MovingIcon } from '../components/NavElements/MovingIcon.jsx' +import { CopyingIcon } from '../components/NavElements/CopyingIcon.jsx' + +export const useTpopfreiwkontrNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const popId = props?.popId ?? params.popId + const tpopId = props?.tpopId ?? params.tpopId + const tpopkontrId = props?.tpopkontrId ?? params.tpopkontrId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: [ + 'treeTpopfreiwkontr', + tpopkontrId, + store.tree.tpopkontrzaehlGqlFilterForTree, + ], + queryFn: () => + apolloClient.query({ + query: gql` + query NavTpopfreiwkontrQuery( + $tpopkontrId: UUID! + $tpopkontrzaehlFilter: TpopkontrzaehlFilter! + ) { + tpopkontrById(id: $tpopkontrId) { + id + label: labelEkf + tpopkontrzaehlsByTpopkontrId { + totalCount + } + tpopkontrFilesByTpopkontrId { + totalCount + } + filteredTpopkontrzaehls: tpopkontrzaehlsByTpopkontrId( + filter: $tpopkontrzaehlFilter + ) { + totalCount + } + } + } + `, + variables: { + tpopkontrId, + tpopkontrzaehlFilter: store.tree.tpopkontrzaehlGqlFilterForTree, + }, + fetchPolicy: 'no-cache', + }), + }) + useEffect( + () => reaction(() => store.tree.tpopkontrzaehlGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + const [, setRerenderer] = useState(0) + const rerender = useCallback(() => setRerenderer((prev) => prev + 1), []) + useEffect( + () => reaction(() => store.moving.id, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.copying.id, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const label = data?.data?.tpopkontrById?.label + const tpopkontrzaehlCount = + data?.data?.tpopkontrById?.tpopkontrzaehlsByTpopkontrId?.totalCount ?? 0 + const filteredTpopkontrzaehlCount = + data?.data?.tpopkontrById?.filteredTpopkontrzaehls?.totalCount ?? 0 + const filesCount = + data?.data?.tpopkontrById?.tpopkontrFilesByTpopkontrId?.totalCount ?? 0 + + const labelRightElements = useMemo(() => { + const labelRightElements = [] + const isMoving = store.moving.id === tpopkontrId + if (isMoving) { + labelRightElements.push(MovingIcon) + } + const isCopying = store.copying.id === tpopkontrId + if (isCopying) { + labelRightElements.push(CopyingIcon) + } + + return labelRightElements + }, [store.copying.id, store.moving.id, tpopkontrId]) + + const navData = useMemo( + () => ({ + id: tpopkontrId, + url: `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Teil-Populationen/${tpopId}/Freiwilligen-Kontrollen/${tpopkontrId}`, + label, + labelRightElements: + labelRightElements.length ? labelRightElements : undefined, + // leave totalCount undefined as the menus are folders + menus: [ + { + id: 'Freiwilligen-Kontrolle', + label: `Freiwilligen-Kontrolle`, + labelRightElements: + labelRightElements.length ? labelRightElements : undefined, + }, + { + id: 'Zaehlungen', + label: `Zählungen (${isLoading ? '...' : `${filteredTpopkontrzaehlCount}/${tpopkontrzaehlCount}`})`, + hideInNavList: true, + }, + { + id: 'Dateien', + label: `Dateien (${filesCount})`, + count: filesCount, + }, + ], + }), + [ + apId, + filesCount, + filteredTpopkontrzaehlCount, + isLoading, + label, + labelRightElements, + popId, + projId, + tpopId, + tpopkontrId, + tpopkontrzaehlCount, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useTpopfreiwkontrsNavData.js b/src/modules/useTpopfreiwkontrsNavData.js new file mode 100644 index 0000000000..288a5bb797 --- /dev/null +++ b/src/modules/useTpopfreiwkontrsNavData.js @@ -0,0 +1,120 @@ +import { useMemo, useEffect, useContext, useState, useCallback } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' +import { useParams } from 'react-router' + +import { MobxContext } from '../mobxContext.js' +import { MovingIcon } from '../components/NavElements/MovingIcon.jsx' +import { CopyingIcon } from '../components/NavElements/CopyingIcon.jsx' + +export const useTpopfreiwkontrsNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const popId = props?.popId ?? params.popId + const tpopId = props?.tpopId ?? params.tpopId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treeTpopfreiwkontr', tpopId, store.tree.ekfGqlFilterForTree], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeTpopfreiwkontrsQuery( + $ekfsFilter: TpopkontrFilter! + $tpopId: UUID! + ) { + tpopById(id: $tpopId) { + id + tpopkontrsByTpopId( + filter: $ekfsFilter + orderBy: [JAHR_ASC, DATUM_ASC] + ) { + totalCount + nodes { + id + label: labelEkf + } + } + totalCount: tpopkontrsByTpopId( + filter: { typ: { equalTo: "Freiwilligen-Erfolgskontrolle" } } + ) { + totalCount + } + } + } + `, + variables: { + ekfsFilter: store.tree.ekfGqlFilterForTree, + tpopId, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.ekfGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + const [, setRerenderer] = useState(0) + const rerender = useCallback(() => setRerenderer((prev) => prev + 1), []) + useEffect( + () => reaction(() => store.moving.id, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.copying.id, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = data?.data?.tpopById?.tpopkontrsByTpopId?.totalCount ?? 0 + const totalCount = data?.data?.tpopById?.totalCount?.totalCount ?? 0 + const menus = useMemo( + () => + (data?.data?.tpopById?.tpopkontrsByTpopId?.nodes ?? []).map((p) => { + const labelRightElements = [] + const isMoving = store.moving.id === p.id + if (isMoving) { + labelRightElements.push(MovingIcon) + } + const isCopying = store.copying.id === p.id + if (isCopying) { + labelRightElements.push(CopyingIcon) + } + + return { + id: p.id, + label: p.label, + labelRightElements: + labelRightElements.length ? labelRightElements : undefined, + } + }), + [ + data?.data?.tpopById?.tpopkontrsByTpopId?.nodes, + store.copying.id, + store.moving.id, + ], + ) + + const navData = useMemo( + () => ({ + id: 'Freiwilligen-Kontrollen', + listFilter: 'tpopkontr', + url: `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Teil-Populationen/${tpopId}/Freiwilligen-Kontrollen`, + label: `Freiwilligen-Kontrollen (${isLoading ? '...' : `${count}/${totalCount}`})`, + labelShort: `EKF (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus, + }), + [apId, count, isLoading, menus, popId, projId, totalCount, tpopId], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useTpopkontrzaehlEinheitWertesNavData.js b/src/modules/useTpopkontrzaehlEinheitWertesNavData.js new file mode 100644 index 0000000000..ced0302953 --- /dev/null +++ b/src/modules/useTpopkontrzaehlEinheitWertesNavData.js @@ -0,0 +1,81 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' + +import { MobxContext } from '../mobxContext.js' + +export const useTpopkontrzaehlEinheitWertesNavData = () => { + const apolloClient = useApolloClient() + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: [ + 'treeTpopkontrzaehlEinheitWerte', + store.tree.tpopkontrzaehlEinheitWerteGqlFilterForTree, + ], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeTpopkontrzaehlEinheitWertesQuery( + $filter: TpopkontrzaehlEinheitWerteFilter! + ) { + allTpopkontrzaehlEinheitWertes( + filter: $filter + orderBy: [SORT_ASC, TEXT_ASC] + ) { + nodes { + id + label + } + } + totalCount: allTpopkontrzaehlEinheitWertes { + totalCount + } + } + `, + variables: { + filter: store.tree.tpopkontrzaehlEinheitWerteGqlFilterForTree, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => + reaction( + () => store.tree.tpopkontrzaehlEinheitWerteGqlFilterForTree, + refetch, + ), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + const count = data?.data?.allTpopkontrzaehlEinheitWertes?.nodes?.length ?? 0 + const totalCount = data?.data?.totalCount?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: 'TpopkontrzaehlEinheitWerte', + listFilter: 'tpopkontrzaehlEinheitWerte', + url: `/Daten/Werte-Listen/TpopkontrzaehlEinheitWerte`, + label: `Teil-Population: Zähl-Einheiten (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus: (data?.data?.allTpopkontrzaehlEinheitWertes?.nodes ?? []).map( + (p) => ({ + id: p.id, + label: p.label, + }), + ), + }), + [ + count, + data?.data?.allTpopkontrzaehlEinheitWertes?.nodes, + isLoading, + totalCount, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useTpopmassnNavData.js b/src/modules/useTpopmassnNavData.js new file mode 100644 index 0000000000..d1b2b3a122 --- /dev/null +++ b/src/modules/useTpopmassnNavData.js @@ -0,0 +1,110 @@ +import { useMemo, useContext, useEffect, useState, useCallback } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { useParams } from 'react-router' +import { reaction } from 'mobx' + +import { MobxContext } from '../mobxContext.js' +import { MovingIcon } from '../components/NavElements/MovingIcon.jsx' +import { CopyingIcon } from '../components/NavElements/CopyingIcon.jsx' + +export const useTpopmassnNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const popId = props?.popId ?? params.popId + const tpopId = props?.tpopId ?? params.tpopId + const tpopmassnId = props?.tpopmassnId ?? params.tpopmassnId + + const store = useContext(MobxContext) + + const { data, isLoading, error } = useQuery({ + queryKey: ['treeTpopmassn', tpopmassnId], + queryFn: () => + apolloClient.query({ + query: gql` + query NavTpopmassnQuery($tpopmassnId: UUID!) { + tpopmassnById(id: $tpopmassnId) { + id + label + tpopmassnFilesByTpopmassnId { + totalCount + } + } + } + `, + variables: { + tpopmassnId, + }, + fetchPolicy: 'no-cache', + }), + }) + const [, setRerenderer] = useState(0) + const rerender = useCallback(() => setRerenderer((prev) => prev + 1), []) + useEffect( + () => reaction(() => store.moving.id, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.copying.id, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const label = data?.data?.tpopmassnById?.label + data?.data?.tpopmassnById?.filteredBeobZugeordnet?.totalCount ?? 0 + const filesCount = + data?.data?.tpopmassnById?.tpopmassnFilesByTpopmassnId?.totalCount ?? 0 + + const labelRightElements = useMemo(() => { + const labelRightElements = [] + const isMoving = store.moving.id === tpopmassnId + if (isMoving) { + labelRightElements.push(MovingIcon) + } + const isCopying = store.copying.id === tpopmassnId + if (isCopying) { + labelRightElements.push(CopyingIcon) + } + + return labelRightElements + }, [store.copying.id, store.moving.id, tpopmassnId]) + + const navData = useMemo( + () => ({ + id: tpopmassnId, + url: `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Teil-Populationen/${tpopId}/Massnahmen/${tpopmassnId}`, + label, + labelRightElements: + labelRightElements.length ? labelRightElements : undefined, + // leave totalCount undefined as the menus are folders + menus: [ + { + id: 'Massnahme', + label: `Massnahme`, + labelRightElements: + labelRightElements.length ? labelRightElements : undefined, + }, + { + id: 'Dateien', + label: `Dateien (${filesCount})`, + count: filesCount, + }, + ], + }), + [ + apId, + filesCount, + label, + labelRightElements, + popId, + projId, + tpopId, + tpopmassnId, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useTpopmassnbersNavData.js b/src/modules/useTpopmassnbersNavData.js new file mode 100644 index 0000000000..7ee034f980 --- /dev/null +++ b/src/modules/useTpopmassnbersNavData.js @@ -0,0 +1,95 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' +import { useParams } from 'react-router' + +import { MobxContext } from '../mobxContext.js' + +export const useTpopmassnbersNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const popId = props?.popId ?? params.popId + const tpopId = props?.tpopId ?? params.tpopId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: [ + 'treeTpopmassnber', + tpopId, + store.tree.tpopmassnberGqlFilterForTree, + ], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeTpopmassnbersQuery( + $tpopmassnbersFilter: TpopmassnberFilter! + $tpopId: UUID! + ) { + tpopById(id: $tpopId) { + id + tpopmassnbersByTpopId( + filter: $tpopmassnbersFilter + orderBy: LABEL_ASC + ) { + totalCount + nodes { + id + label + } + } + totalCount: tpopmassnbersByTpopId { + totalCount + } + } + } + `, + variables: { + tpopmassnbersFilter: store.tree.tpopmassnberGqlFilterForTree, + tpopId, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.tpopmassnberGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = data?.data?.tpopById?.tpopmassnbersByTpopId?.totalCount ?? 0 + const totalCount = data?.data?.tpopById?.totalCount?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: 'Massnahmen-Berichte', + listFilter: 'tpopmassnber', + url: `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Teil-Populationen/${tpopId}/Massnahmen-Berichte`, + label: `Massnahmen-Berichte (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus: (data?.data?.tpopById?.tpopmassnbersByTpopId?.nodes ?? []).map( + (p) => ({ + id: p.id, + label: p.label, + }), + ), + }), + [ + apId, + count, + data?.data?.tpopById?.tpopmassnbersByTpopId?.nodes, + isLoading, + popId, + projId, + totalCount, + tpopId, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useTpopmassnsNavData.js b/src/modules/useTpopmassnsNavData.js new file mode 100644 index 0000000000..190118da0e --- /dev/null +++ b/src/modules/useTpopmassnsNavData.js @@ -0,0 +1,121 @@ +import { useMemo, useEffect, useContext, useState, useCallback } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' +import { useParams } from 'react-router' + +import { MobxContext } from '../mobxContext.js' +import { MovingIcon } from '../components/NavElements/MovingIcon.jsx' +import { CopyingIcon } from '../components/NavElements/CopyingIcon.jsx' + +export const useTpopmassnsNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const popId = props?.popId ?? params.popId + const tpopId = props?.tpopId ?? params.tpopId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treeTpopmassn', tpopId, store.tree.tpopmassnGqlFilterForTree], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeTpopmassnsQuery( + $tpopmassnsFilter: TpopmassnFilter! + $tpopId: UUID! + ) { + tpopById(id: $tpopId) { + id + tpopmassnsByTpopId( + filter: $tpopmassnsFilter + orderBy: [JAHR_ASC, DATUM_ASC] + ) { + totalCount + nodes { + id + label + } + } + totalCount: tpopmassnsByTpopId { + totalCount + } + } + } + `, + variables: { + tpopmassnsFilter: store.tree.tpopmassnGqlFilterForTree, + tpopId, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.tpopmassnGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + const [, setRerenderer] = useState(0) + const rerender = useCallback(() => setRerenderer((prev) => prev + 1), []) + useEffect( + () => reaction(() => store.moving.id, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.copying.id, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = data?.data?.tpopById?.tpopmassnsByTpopId?.totalCount ?? 0 + const totalCount = data?.data?.tpopById?.totalCount?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: 'Massnahmen', + listFilter: 'tpopmassn', + url: `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Teil-Populationen/${tpopId}/Massnahmen`, + label: `Massnahmen (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus: (data?.data?.tpopById?.tpopmassnsByTpopId?.nodes ?? []).map( + (p) => { + const labelRightElements = [] + const isMoving = store.moving.id === p.id + if (isMoving) { + labelRightElements.push(MovingIcon) + } + const isCopying = store.copying.id === p.id + if (isCopying) { + labelRightElements.push(CopyingIcon) + } + + return { + id: p.id, + label: p.label, + labelRightElements: + labelRightElements.length ? labelRightElements : undefined, + } + }, + ), + }), + [ + apId, + count, + data?.data?.tpopById?.tpopmassnsByTpopId?.nodes, + isLoading, + popId, + projId, + store.copying.id, + store.moving.id, + totalCount, + tpopId, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useTpopsNavData.js b/src/modules/useTpopsNavData.js new file mode 100644 index 0000000000..acf2b21d33 --- /dev/null +++ b/src/modules/useTpopsNavData.js @@ -0,0 +1,211 @@ +import { useMemo, useEffect, useContext, useState, useCallback } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' +import { useParams } from 'react-router' + +import { MobxContext } from '../mobxContext.js' + +import { TpopIcon100 } from '../components/Projekte/Karte/layers/Tpop/statusGroupSymbols/100.jsx' +import { TpopIcon100Highlighted } from '../components/Projekte/Karte/layers/Tpop/statusGroupSymbols/100Highlighted.jsx' +import { TpopIcon101 } from '../components/Projekte/Karte/layers/Tpop/statusGroupSymbols/101.jsx' +import { TpopIcon101Highlighted } from '../components/Projekte/Karte/layers/Tpop/statusGroupSymbols/101Highlighted.jsx' +import { TpopIcon200 } from '../components/Projekte/Karte/layers/Tpop/statusGroupSymbols/200.jsx' +import { TpopIcon200Highlighted } from '../components/Projekte/Karte/layers/Tpop/statusGroupSymbols/200Highlighted.jsx' +import { TpopIcon201 } from '../components/Projekte/Karte/layers/Tpop/statusGroupSymbols/201.jsx' +import { TpopIcon201Highlighted } from '../components/Projekte/Karte/layers/Tpop/statusGroupSymbols/201Highlighted.jsx' +import { TpopIcon202 } from '../components/Projekte/Karte/layers/Tpop/statusGroupSymbols/202.jsx' +import { TpopIcon202Highlighted } from '../components/Projekte/Karte/layers/Tpop/statusGroupSymbols/202Highlighted.jsx' +import { TpopIcon300 } from '../components/Projekte/Karte/layers/Tpop/statusGroupSymbols/300.jsx' +import { TpopIcon300Highlighted } from '../components/Projekte/Karte/layers/Tpop/statusGroupSymbols/300Highlighted.jsx' +import { TpopIcon } from '../components/Projekte/Karte/layers/Tpop/tpop.jsx' +import { TpopIconHighlighted } from '../components/Projekte/Karte/layers/Tpop/tpopHighlighted.jsx' +import { TpopIconU } from '../components/Projekte/Karte/layers/Tpop/statusGroup/u.jsx' +import { TpopIconUHighlighted } from '../components/Projekte/Karte/layers/Tpop/statusGroup/uHighlighted.jsx' +import { TpopIconA } from '../components/Projekte/Karte/layers/Tpop/statusGroup/a.jsx' +import { TpopIconAHighlighted } from '../components/Projekte/Karte/layers/Tpop/statusGroup/aHighlighted.jsx' +import { TpopIconP } from '../components/Projekte/Karte/layers/Tpop/statusGroup/p.jsx' +import { TpopIconPHighlighted } from '../components/Projekte/Karte/layers/Tpop/statusGroup/pHighlighted.jsx' +import { TpopIconQ } from '../components/Projekte/Karte/layers/Tpop/statusGroup/q.jsx' +import { TpopIconQHighlighted } from '../components/Projekte/Karte/layers/Tpop/statusGroup/qHighlighted.jsx' + +import { MovingIcon } from '../components/NavElements/MovingIcon.jsx' +import { CopyingIcon } from '../components/NavElements/CopyingIcon.jsx' + +export const tpopIcons = { + normal: { + 100: TpopIcon, + '100Highlighted': TpopIconHighlighted, + 101: TpopIcon, + '101Highlighted': TpopIconHighlighted, + 200: TpopIcon, + '200Highlighted': TpopIconHighlighted, + 201: TpopIcon, + '201Highlighted': TpopIconHighlighted, + 202: TpopIcon, + '202Highlighted': TpopIconHighlighted, + 300: TpopIcon, + '300Highlighted': TpopIconHighlighted, + }, + statusGroup: { + 100: TpopIconU, + '100Highlighted': TpopIconUHighlighted, + 101: TpopIconU, + '101Highlighted': TpopIconUHighlighted, + 200: TpopIconA, + '200Highlighted': TpopIconAHighlighted, + 201: TpopIconA, + '201Highlighted': TpopIconAHighlighted, + 202: TpopIconA, + '202Highlighted': TpopIconAHighlighted, + 300: TpopIconP, + '300Highlighted': TpopIconPHighlighted, + }, + statusGroupSymbols: { + 100: TpopIcon100, + '100Highlighted': TpopIcon100Highlighted, + 101: TpopIcon101, + '101Highlighted': TpopIcon101Highlighted, + 200: TpopIcon200, + '200Highlighted': TpopIcon200Highlighted, + 201: TpopIcon201, + '201Highlighted': TpopIcon201Highlighted, + 202: TpopIcon202, + '202Highlighted': TpopIcon202Highlighted, + 300: TpopIcon300, + '300Highlighted': TpopIcon300Highlighted, + }, +} + +export const useTpopsNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const popId = props?.popId ?? params.popId + const tpopId = props?.tpopId ?? params.tpopId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treeTpop', popId, store.tree.tpopGqlFilterForTree], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeTpopsQuery($tpopsFilter: TpopFilter!, $popId: UUID!) { + popById(id: $popId) { + id + tpopsByPopId( + filter: $tpopsFilter + orderBy: [NR_ASC, FLURNAME_ASC] + ) { + totalCount + nodes { + id + label + status + } + } + totalCount: tpopsByPopId { + totalCount + } + } + } + `, + variables: { + tpopsFilter: store.tree.tpopGqlFilterForTree, + popId, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.tpopGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + const [, setRerenderer] = useState(0) + const rerender = useCallback(() => setRerenderer((prev) => prev + 1), []) + useEffect( + () => reaction(() => store.map.tpopIcon, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.tree.showTpopIcon, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.moving.id, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => reaction(() => store.copying.id, rerender), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = data?.data?.popById?.tpopsByPopId?.nodes?.length ?? 0 + const totalCount = data?.data?.popById?.totalCount?.totalCount ?? 0 + + const tpopIconName = store.map.tpopIcon + + const navData = useMemo( + () => ({ + id: 'Teil-Populationen', + listFilter: 'tpop', + url: `/Daten/Projekte/${projId}/Arten/${apId}/Populationen/${popId}/Teil-Populationen`, + label: `Teil-Populationen (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus: (data?.data?.popById?.tpopsByPopId?.nodes ?? []).map((p) => { + const labelRightElements = [] + const isMoving = store.moving.id === p.id + if (isMoving) { + labelRightElements.push(MovingIcon) + } + const isCopying = store.copying.id === p.id + if (isCopying) { + labelRightElements.push(CopyingIcon) + } + + const iconIsHighlighted = p.id === tpopId + const TpopIcon = + p.status ? + iconIsHighlighted ? + tpopIcons[tpopIconName][p.status + 'Highlighted'] + : tpopIcons[tpopIconName][p.status] + : iconIsHighlighted ? TpopIconQHighlighted + : TpopIconQ + + return { + id: p.id, + label: p.label, + status: p.status, + labelLeftElements: store.tree.showTpopIcon ? [TpopIcon] : undefined, + labelRightElements: + labelRightElements.length ? labelRightElements : undefined, + } + }), + }), + [ + apId, + count, + data?.data?.popById?.tpopsByPopId?.nodes, + isLoading, + popId, + projId, + store.copying.id, + store.moving.id, + store.tree.showTpopIcon, + totalCount, + tpopIconName, + tpopId, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useUsersNavData.js b/src/modules/useUsersNavData.js new file mode 100644 index 0000000000..e594d7befe --- /dev/null +++ b/src/modules/useUsersNavData.js @@ -0,0 +1,63 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' + +import { MobxContext } from '../mobxContext.js' + +export const useUsersNavData = () => { + const apolloClient = useApolloClient() + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treeUser', store.tree.userGqlFilterForTree], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeUsersQuery($usersFilter: UserFilter!) { + allUsers(filter: $usersFilter, orderBy: LABEL_ASC) { + nodes { + id + label + } + } + totalCount: allUsers { + totalCount + } + } + `, + variables: { + usersFilter: store.tree.userGqlFilterForTree, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.userGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = data?.data?.allUsers?.nodes?.length ?? 0 + const totalCount = data?.data?.totalCount?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: 'Benutzer', + listFilter: 'user', + url: `/Daten/Benutzer`, + label: `Benutzer (${isLoading ? '...' : `${count}/${totalCount}`})`, + menus: (data?.data?.allUsers?.nodes ?? []).map((p) => ({ + id: p.id, + label: p.label, + })), + }), + [count, data?.data?.allUsers?.nodes, isLoading, totalCount], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useWertesNavData.js b/src/modules/useWertesNavData.js new file mode 100644 index 0000000000..8fb262c658 --- /dev/null +++ b/src/modules/useWertesNavData.js @@ -0,0 +1,166 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' + +import { MobxContext } from '../mobxContext.js' + +export const useWertesNavData = (props) => { + const apolloClient = useApolloClient() + const include = props?.include ?? true + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: [ + 'treeWertes', + store.tree.adresseGqlFilterForTree, + store.tree.tpopApberrelevantGrundWerteGqlFilterForTree, + store.tree.ekAbrechnungstypWerteGqlFilterForTree, + store.tree.tpopkontrzaehlEinheitWerteGqlFilterForTree, + ], + queryFn: () => + apolloClient.query({ + query: gql` + query NavWertesQuery( + $adressesFilter: AdresseFilter! + $tpopApberrelevantGrundWerteFilter: TpopApberrelevantGrundWerteFilter! + $ekAbrechnungstypWerteFilter: EkAbrechnungstypWerteFilter! + $tpopkontrzaehlEinheitWerteFilter: TpopkontrzaehlEinheitWerteFilter! + $include: Boolean! + ) { + allAdresses @include(if: $include) { + totalCount + } + filteredAdresses: allAdresses(filter: $adressesFilter) + @include(if: $include) { + totalCount + } + allTpopApberrelevantGrundWertes @include(if: $include) { + totalCount + } + filteredTpopApberrelevantGrundWertes: allTpopApberrelevantGrundWertes( + filter: $tpopApberrelevantGrundWerteFilter + ) @include(if: $include) { + totalCount + } + allEkAbrechnungstypWertes @include(if: $include) { + totalCount + } + filteredEkAbrechnungstypWertes: allEkAbrechnungstypWertes( + filter: $ekAbrechnungstypWerteFilter + ) @include(if: $include) { + totalCount + } + allTpopkontrzaehlEinheitWertes @include(if: $include) { + totalCount + } + filteredTpopkontrzaehlEinheitWertes: allTpopkontrzaehlEinheitWertes( + filter: $tpopkontrzaehlEinheitWerteFilter + ) @include(if: $include) { + totalCount + } + } + `, + variables: { + adressesFilter: store.tree.adresseGqlFilterForTree, + tpopApberrelevantGrundWerteFilter: + store.tree.tpopApberrelevantGrundWerteGqlFilterForTree, + ekAbrechnungstypWerteFilter: + store.tree.ekAbrechnungstypWerteGqlFilterForTree, + tpopkontrzaehlEinheitWerteFilter: + store.tree.tpopkontrzaehlEinheitWerteGqlFilterForTree, + include, + }, + fetchPolicy: 'no-cache', + }), + }) + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.adresseGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => + reaction( + () => store.tree.tpopApberrelevantGrundWerteGqlFilterForTree, + refetch, + ), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => + reaction(() => store.tree.ekAbrechnungstypWerteGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + useEffect( + () => + reaction( + () => store.tree.tpopkontrzaehlEinheitWerteGqlFilterForTree, + refetch, + ), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + const adressesCount = data?.data?.allAdresses?.totalCount ?? 0 + const adressesFilteredCount = data?.data?.filteredAdresses?.totalCount ?? 0 + const tpopApberrelevantGrundWerteCount = + data?.data?.allTpopApberrelevantGrundWertes?.totalCount ?? 0 + const tpopApberrelevantGrundWerteFilteredCount = + data?.data?.filteredTpopApberrelevantGrundWertes?.totalCount ?? 0 + const ekAbrechnungstypWerteCount = + data?.data?.allEkAbrechnungstypWertes?.totalCount ?? 0 + const ekAbrechnungstypWerteFilteredCount = + data?.data?.filteredEkAbrechnungstypWertes?.totalCount ?? 0 + const tpopkontrzaehlEinheitWerteCount = + data?.data?.allTpopkontrzaehlEinheitWertes?.totalCount ?? 0 + const tpopkontrzaehlEinheitWerteFilteredCount = + data?.data?.filteredTpopkontrzaehlEinheitWertes?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: 'WerteListen', + url: '/Daten/Werte-Listen', + label: `Werte-Listen`, + // leave totalCount undefined as the menus are folders + menus: [ + { + id: 'Adressen', + label: `Adressen (${isLoading ? '...' : `${adressesFilteredCount}/${adressesCount}`})`, + count: adressesCount, + }, + { + id: 'ApberrelevantGrundWerte', + label: `Teil-Population: Grund für AP-Bericht Relevanz (${isLoading ? '...' : `${tpopApberrelevantGrundWerteFilteredCount}/${tpopApberrelevantGrundWerteCount}`})`, + count: tpopApberrelevantGrundWerteCount, + }, + { + id: 'EkAbrechnungstypWerte', + label: `Teil-Population: EK-Abrechnungstypen (${isLoading ? '...' : `${ekAbrechnungstypWerteFilteredCount}/${ekAbrechnungstypWerteCount}`})`, + count: ekAbrechnungstypWerteCount, + }, + { + id: 'TpopkontrzaehlEinheitWerte', + label: `Teil-Population: Zähl-Einheiten (${isLoading ? '...' : `${tpopkontrzaehlEinheitWerteFilteredCount}/${tpopkontrzaehlEinheitWerteCount}`})`, + count: tpopkontrzaehlEinheitWerteCount, + }, + ], + }), + [ + adressesCount, + adressesFilteredCount, + ekAbrechnungstypWerteCount, + ekAbrechnungstypWerteFilteredCount, + isLoading, + tpopApberrelevantGrundWerteCount, + tpopApberrelevantGrundWerteFilteredCount, + tpopkontrzaehlEinheitWerteCount, + tpopkontrzaehlEinheitWerteFilteredCount, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useZielNavData.js b/src/modules/useZielNavData.js new file mode 100644 index 0000000000..89b806cd2c --- /dev/null +++ b/src/modules/useZielNavData.js @@ -0,0 +1,88 @@ +import { useMemo, useContext, useEffect } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { useParams } from 'react-router' +import { reaction } from 'mobx' + +import { MobxContext } from '../mobxContext.js' + +export const useZielNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const jahr = props?.jahr ?? params.jahr + const zielId = props?.zielId ?? params.zielId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treeZiel', zielId, store.tree.zielberGqlFilterForTree], + queryFn: () => + apolloClient.query({ + query: gql` + query NavZielQuery($zielId: UUID!, $zielberFilter: ZielberFilter!) { + zielById(id: $zielId) { + id + label + zielbersByZielId { + totalCount + } + filteredZielbers: zielbersByZielId( + filter: $zielberFilter + orderBy: LABEL_ASC + ) { + totalCount + } + } + } + `, + variables: { + zielId, + zielberFilter: store.tree.zielberGqlFilterForTree, + }, + fetchPolicy: 'no-cache', + }), + }) + useEffect( + () => reaction(() => store.tree.zielberGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const zielbersCount = data?.data?.zielById?.zielbersByZielId?.totalCount ?? 0 + const filteredZielbersCount = + data?.data?.zielById?.filteredZielbers?.totalCount ?? 0 + + const navData = useMemo( + () => ({ + id: zielId, + url: `/Daten/Projekte/${projId}/Arten/${apId}/AP-Ziele/${jahr}/${zielId}`, + label: data?.data?.zielById?.label ?? '(nicht beschrieben)', + // leave totalCount undefined as the menus are folders + menus: [ + { + id: 'Ziel', + label: 'Ziel', + }, + { + id: 'Berichte', + label: `Berichte (${isLoading ? '...' : `${filteredZielbersCount}/${zielbersCount}`})`, + count: zielbersCount, + }, + ], + }), + [ + apId, + data?.data?.zielById?.label, + filteredZielbersCount, + isLoading, + jahr, + projId, + zielId, + zielbersCount, + ], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useZielbersNavData.js b/src/modules/useZielbersNavData.js new file mode 100644 index 0000000000..f6b0abfa56 --- /dev/null +++ b/src/modules/useZielbersNavData.js @@ -0,0 +1,82 @@ +import { useMemo, useContext, useEffect } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { useParams } from 'react-router' +import { reaction } from 'mobx' + +import { MobxContext } from '../mobxContext.js' + +export const useZielbersNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const jahr = props?.jahr ?? params.jahr + const zielId = props?.zielId ?? params.zielId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treeZielber', zielId, store.tree.zielberGqlFilterForTree], + queryFn: () => + apolloClient.query({ + query: gql` + query NavZielbersQuery( + $zielId: UUID! + $zielberFilter: ZielberFilter! + ) { + zielById(id: $zielId) { + id + label + zielbersByZielId { + totalCount + } + filteredZielbers: zielbersByZielId( + filter: $zielberFilter + orderBy: LABEL_ASC + ) { + totalCount + nodes { + id + label + } + } + } + } + `, + variables: { + zielId, + zielberFilter: store.tree.zielberGqlFilterForTree, + }, + fetchPolicy: 'no-cache', + }), + }) + useEffect( + () => reaction(() => store.tree.zielberGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = data?.data?.zielById?.zielbersByZielId?.totalCount ?? 0 + const filteredCount = data?.data?.zielById?.filteredZielbers?.totalCount ?? 0 + const menus = (data?.data?.zielById?.filteredZielbers?.nodes ?? []).map( + (zielber) => ({ + id: zielber.id, + label: zielber.label, + }), + ) + + const navData = useMemo( + () => ({ + id: 'Berichte', + listFilter: 'zielber', + url: `/Daten/Projekte/${projId}/Arten/${apId}/AP-Ziele/${jahr}/${zielId}/Berichte`, + label: `Zielberichte (${isLoading ? '...' : `${filteredCount}/${count}`})`, + // leave totalCount undefined as the menus are folders + menus, + }), + [apId, count, filteredCount, isLoading, jahr, menus, projId, zielId], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useZieljahrsNavData.js b/src/modules/useZieljahrsNavData.js new file mode 100644 index 0000000000..286641deec --- /dev/null +++ b/src/modules/useZieljahrsNavData.js @@ -0,0 +1,99 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' +import { useParams } from 'react-router' +import countBy from 'lodash/countBy' + +import { MobxContext } from '../mobxContext.js' + +export const useZieljahrsNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treeZieljahrs', apId, store.tree.zielGqlFilterForTree], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeZieljahrsQuery($zielsFilter: ZielFilter!, $apId: UUID!) { + apById(id: $apId) { + id + zielsByApId { + nodes { + id + jahr + } + } + filteredZiels: zielsByApId( + filter: $zielsFilter + orderBy: [JAHR_ASC, LABEL_ASC] + ) { + nodes { + id + label + jahr + } + } + } + } + `, + variables: { + zielsFilter: store.tree.zielGqlFilterForTree, + apId, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.zielGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const ziels = useMemo( + () => data?.data?.apById?.zielsByApId?.nodes ?? [], + [data?.data?.apById?.zielsByApId?.nodes], + ) + const filteredZiels = useMemo( + () => data?.data?.apById?.filteredZiels?.nodes ?? [], + [data?.data?.apById?.filteredZiels?.nodes], + ) + const zieljahrsCount = useMemo(() => { + const jahrs = countBy(ziels, 'jahr') + const count = Object.keys(jahrs).length + return count + }, [ziels]) + + const menus = useMemo(() => { + const countByJahr = countBy(filteredZiels, 'jahr') + const unfilteredCountByJahr = countBy(ziels, 'jahr') + // convert into array of objects with id=jahr and count + const jahre = Object.keys(countByJahr).map((jahr) => ({ + id: +jahr, + label: `${jahr} (${countByJahr[jahr]}/${unfilteredCountByJahr[jahr]})`, + jahr: +jahr, + })) + return jahre + }, [filteredZiels, ziels]) + + const navData = useMemo( + () => ({ + id: 'AP-Ziele', + listFilter: 'ziel', + url: `/Daten/Projekte/${projId}/Arten/${apId}/AP-Ziele`, + label: `AP-Ziele Jahre (${isLoading ? '...' : `${menus.length}/${zieljahrsCount}`})`, + menus, + }), + [apId, menus, isLoading, projId, zieljahrsCount], + ) + + return { isLoading, error, navData } +} diff --git a/src/modules/useZielsOfJahrNavData.js b/src/modules/useZielsOfJahrNavData.js new file mode 100644 index 0000000000..d0f677c659 --- /dev/null +++ b/src/modules/useZielsOfJahrNavData.js @@ -0,0 +1,91 @@ +import { useMemo, useEffect, useContext } from 'react' +import { useApolloClient, gql } from '@apollo/client' +import { useQuery } from '@tanstack/react-query' +import { reaction } from 'mobx' +import { useParams } from 'react-router' + +import { MobxContext } from '../mobxContext.js' + +export const useZielsOfJahrNavData = (props) => { + const apolloClient = useApolloClient() + const params = useParams() + const projId = props?.projId ?? params.projId + const apId = props?.apId ?? params.apId + const jahr = props?.jahr ?? params.jahr + + const store = useContext(MobxContext) + + const { data, isLoading, error, refetch } = useQuery({ + queryKey: ['treeZielsOfJahr', apId, jahr, store.tree.zielGqlFilterForTree], + queryFn: () => + apolloClient.query({ + query: gql` + query TreeZielsOfJahrQuery( + $zielsFilter: ZielFilter! + $jahrFilter: ZielFilter! + $apId: UUID! + ) { + apById(id: $apId) { + id + zielsByApId(filter: $jahrFilter) { + totalCount + } + filteredZiels: zielsByApId( + filter: $zielsFilter + orderBy: [JAHR_ASC, LABEL_ASC] + ) { + nodes { + id + label + jahr + } + } + } + } + `, + variables: { + jahrFilter: { + jahr: { equalTo: +jahr }, + }, + zielsFilter: { + ...store.tree.zielGqlFilterForTree, + jahr: { equalTo: +jahr }, + }, + apId, + }, + fetchPolicy: 'no-cache', + }), + }) + // this is how to make the filter reactive in a hook + // see: https://stackoverflow.com/a/72229014/712005 + // react to filter changes without observer (https://stackoverflow.com/a/72229014/712005) + useEffect( + () => reaction(() => store.tree.zielGqlFilterForTree, refetch), + // eslint-disable-next-line react-hooks/exhaustive-deps + [], + ) + + const count = data?.data?.apById?.zielsByApId?.totalCount ?? 0 + const filteredZiels = useMemo( + () => data?.data?.apById?.filteredZiels?.nodes ?? [], + [data?.data?.apById?.filteredZiels?.nodes], + ) + + const navData = useMemo( + () => ({ + id: jahr, + listFilter: 'ziel', + url: `/Daten/Projekte/${projId}/Arten/${apId}/AP-Ziele/${jahr}`, + label: `Ziele für ${jahr} (${isLoading ? '...' : `${filteredZiels.length}/${count}`})`, + labelShort: `${jahr} (${isLoading ? '...' : `${filteredZiels.length}/${count}`})`, + menus: filteredZiels.map((p) => ({ + id: p.id, + label: p.label, + jahr: p.jahr, + })), + }), + [apId, count, filteredZiels, isLoading, jahr, projId], + ) + + return { isLoading, error, navData } +} diff --git a/src/store/Tree/index.js b/src/store/Tree/index.js index 2622044c82..bf6c5f2cce 100644 --- a/src/store/Tree/index.js +++ b/src/store/Tree/index.js @@ -215,7 +215,8 @@ export const Tree = types if (aNA[4] === 'EK-Zähleinheiten') return 'ekzaehleinheit' if (aNA[4] === 'nicht-beurteilte-Beobachtungen') return 'beob' if (aNA[4] === 'nicht-zuzuordnende-Beobachtungen') return 'beob' - if (aNA[4] === 'Qualitaetskontrollen') return undefined + if (aNA[4] === 'Qualitätskontrollen') return undefined + if (aNA[4] === 'Qualitätskontrollen-wählen') return undefined } if (aNA.length > 2) { if (aNA[2] === 'Arten') return 'ap' @@ -1515,7 +1516,7 @@ export const Tree = types // 2. build data filter const filterArray = [] for (const filter of filterArrayInStore) { - // add hiearchy filter + // add hierarchy filter const singleFilter = {} // add data filter const dataFilter = { ...filter } @@ -1710,7 +1711,7 @@ export const Tree = types // 2. build data filter const filterArray = [] for (const filter of filterArrayInStore) { - // add hiearchy filter + // add hierarchy filter const singleFilter = {} // add data filter const dataFilter = { ...filter } @@ -1737,7 +1738,7 @@ export const Tree = types coveredBy: self.mapFilter, } } - // Object needt to filter by typ + // Object needs to filter by typ if (!singleFilter.typ) { singleFilter.typ = { equalTo: 'Freiwilligen-Erfolgskontrolle' } } @@ -1897,6 +1898,73 @@ export const Tree = types return beobGqlFilter }, + get beobNichtBeurteiltGqlFilterForTree() { + const filter = { + wgs84Lat: { isNull: false }, + tpopId: { isNull: true }, + nichtZuordnen: { equalTo: false }, + } + + // node label filter + if (self.nodeLabelFilter.beob) { + filter.label = { + includesInsensitive: self.nodeLabelFilter.beob, + } + } + + // mapFilter + if (self.mapFilter) { + filter.geomPoint = { + coveredBy: self.mapFilter, + } + } + + return filter + }, + get beobNichtZuzuordnenGqlFilterForTree() { + const filter = { + wgs84Lat: { isNull: false }, + nichtZuordnen: { equalTo: true }, + } + + // node label filter + if (self.nodeLabelFilter.beob) { + filter.label = { + includesInsensitive: self.nodeLabelFilter.beob, + } + } + + // mapFilter + if (self.mapFilter) { + filter.geomPoint = { + coveredBy: self.mapFilter, + } + } + + return filter + }, + get beobZugeordnetGqlFilterForTree() { + const filter = { + wgs84Lat: { isNull: false }, + tpopId: { isNull: false }, + } + + // node label filter + if (self.nodeLabelFilter.beob) { + filter.label = { + includesInsensitive: self.nodeLabelFilter.beob, + } + } + + // mapFilter + if (self.mapFilter) { + filter.geomPoint = { + coveredBy: self.mapFilter, + } + } + + return filter + }, beobGqlFilterForTree(type) { const filter = { wgs84Lat: { isNull: false }, diff --git a/src/storeContext.js b/src/storeContext.js deleted file mode 100644 index aabf5320de..0000000000 --- a/src/storeContext.js +++ /dev/null @@ -1,5 +0,0 @@ -import { createContext } from 'react' - -export const StoreContext = createContext({}) -export const Provider = StoreContext.Provider -export const Consumer = StoreContext.Consumer