diff --git a/pwa/package-lock.json b/pwa/package-lock.json index 965dd77d..b5b625ed 100644 --- a/pwa/package-lock.json +++ b/pwa/package-lock.json @@ -8,8 +8,8 @@ "name": "product-website-template", "version": "1.0.0", "dependencies": { - "@conduction/components": "2.2.16", - "@conduction/theme": "1.0.48", + "@conduction/components": "2.2.18", + "@conduction/theme": "1.0.50", "@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/react-fontawesome": "^0.1.18", @@ -41,9 +41,10 @@ "@nl-design-system-unstable/zwolle-design-tokens": "^1.0.0-alpha.100", "@parcel/watcher": "^2.3.0", "@tabler/icons-react": "2.21.0", + "@types/qs": "^6.9.9", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", - "@utrecht/component-library-react": "^1.0.0-alpha.355", + "@utrecht/component-library-react": "^1.0.0-alpha.394", "@utrecht/design-tokens": "^1.0.0-alpha.524", "axios": "^0.25.0", "clsx": "^1.1.1", @@ -56,6 +57,7 @@ "i18next": "^21.6.16", "jwt-decode": "^3.1.2", "lodash": "^4.17.21", + "qs": "^6.11.2", "react": "^18.2.0", "react-collapsible": "^2.10.0", "react-dom": "^18.2.0", @@ -135,15 +137,73 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.5", + "version": "7.22.13", "license": "MIT", "dependencies": { - "@babel/highlight": "^7.22.5" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "license": "MIT" + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/compat-data": { "version": "7.22.9", "license": "MIT", @@ -217,10 +277,10 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.9", + "version": "7.23.0", "license": "MIT", "dependencies": { - "@babel/types": "^7.22.5", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -353,18 +413,18 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", + "version": "7.22.20", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", + "version": "7.23.0", "license": "MIT", "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -502,7 +562,7 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", + "version": "7.22.20", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -540,11 +600,11 @@ } }, "node_modules/@babel/highlight": { - "version": "7.22.5", + "version": "7.22.20", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -609,7 +669,7 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.7", + "version": "7.23.0", "license": "MIT", "bin": { "parser": "bin/babel-parser.js" @@ -1940,29 +2000,29 @@ } }, "node_modules/@babel/template": { - "version": "7.22.5", + "version": "7.22.15", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.22.8", + "version": "7.23.2", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.7", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.7", - "@babel/types": "^7.22.5", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1978,11 +2038,11 @@ } }, "node_modules/@babel/types": { - "version": "7.22.5", + "version": "7.23.0", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1997,9 +2057,9 @@ } }, "node_modules/@conduction/components": { - "version": "2.2.16", - "resolved": "https://registry.npmjs.org/@conduction/components/-/components-2.2.16.tgz", - "integrity": "sha512-XvPNtjuNzwUegoln7uZjBNxjUInpbWI8Oie+8XOhlDssDVZq/t1mYka/eXn8n/chdOwxD5vZ3qZZjU7L4UbSoA==", + "version": "2.2.18", + "resolved": "https://registry.npmjs.org/@conduction/components/-/components-2.2.18.tgz", + "integrity": "sha512-dXuDeA4BVGtNzQ0w2t2J3tDoqEHEOehwd0YYm68NWCDYMy/EtlX8tJpTrQSbXpkKt/pQV6dGt5WuBknWHOPHkA==", "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.2.0", "@fortawesome/free-solid-svg-icons": "^6.2.0", @@ -2052,9 +2112,9 @@ } }, "node_modules/@conduction/theme": { - "version": "1.0.48", - "resolved": "https://registry.npmjs.org/@conduction/theme/-/theme-1.0.48.tgz", - "integrity": "sha512-5XRYHl9W0NvBxVJBZ1/SYyhNYV4hHpm/9taDEw1hjuGzzzyz+XhpN2UMkbtiqFwgN/U6pcZMdxfJIcsw49iI0Q==", + "version": "1.0.50", + "resolved": "https://registry.npmjs.org/@conduction/theme/-/theme-1.0.50.tgz", + "integrity": "sha512-VdkN12+r12WORjVtl2W1kSLMaeZLtqRdrwtHJKuuUFGZmUnqDoxrdjPnX9c5in+MROLgIbBN1Xp/8/Ucy/Y6Lg==", "dependencies": { "@nl-design-system-unstable/rotterdam-design-tokens": "^1.0.0-alpha.100" } @@ -3668,7 +3728,8 @@ }, "node_modules/@popperjs/core": { "version": "2.11.8", - "license": "MIT", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -3939,6 +4000,11 @@ "version": "15.7.5", "license": "MIT" }, + "node_modules/@types/qs": { + "version": "6.9.9", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", + "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==" + }, "node_modules/@types/reach__router": { "version": "1.3.11", "license": "MIT", @@ -4292,16 +4358,30 @@ } }, "node_modules/@utrecht/component-library-react": { - "version": "1.0.0-alpha.356", - "license": "EUPL-1.2", + "version": "1.0.0-alpha.406", + "resolved": "https://registry.npmjs.org/@utrecht/component-library-react/-/component-library-react-1.0.0-alpha.406.tgz", + "integrity": "sha512-lZoV/O4EFDIYghVF8okdXBNlmal7bcvZ1jqNk+b1gac54TwpvTmmbd4dlwB2+kulN1vhwiFN+oUII3zOMuYCuQ==", "dependencies": { "clsx": "1.2.1", - "date-fns": "2.30.0", "lodash.chunk": "4.2.0" }, "peerDependencies": { + "date-fns": "^2.30.0", "react": "18", - "react-dom": "18" + "react-dom": "18", + "react-vega": "^7.6.0", + "vega": "^5.25.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "react-vega": { + "optional": true + }, + "vega": { + "optional": true + } } }, "node_modules/@utrecht/design-tokens": { @@ -5308,6 +5388,20 @@ "version": "2.0.0", "license": "MIT" }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/boolbase": { "version": "1.0.0", "license": "ISC" @@ -5677,7 +5771,8 @@ }, "node_modules/classnames": { "version": "2.3.2", - "license": "MIT" + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz", + "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==" }, "node_modules/clean-stack": { "version": "2.2.0", @@ -7829,6 +7924,20 @@ "version": "2.0.0", "license": "MIT" }, + "node_modules/express/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/ext": { "version": "1.7.0", "license": "ISC", @@ -12826,7 +12935,7 @@ "license": "MIT" }, "node_modules/postcss": { - "version": "8.4.27", + "version": "8.4.31", "funding": [ { "type": "opencollective", @@ -13454,8 +13563,9 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "license": "BSD-3-Clause", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", "dependencies": { "side-channel": "^1.0.4" }, @@ -13602,7 +13712,8 @@ }, "node_modules/react-datepicker": { "version": "4.20.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-4.20.0.tgz", + "integrity": "sha512-I29yHN9SabUDSy7Xq3P8+E8E+D2vyeuYAYYWWjeMisGGtsatltV4CSHodyA7W9z0BuGycc/bhSClDbizx4gZHA==", "dependencies": { "@popperjs/core": "^2.11.8", "classnames": "^2.2.6", @@ -13781,7 +13892,8 @@ }, "node_modules/react-onclickoutside": { "version": "6.13.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz", + "integrity": "sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==", "funding": { "type": "individual", "url": "https://github.com/Pomax/react-onclickoutside/blob/master/FUNDING.md" @@ -13803,7 +13915,8 @@ }, "node_modules/react-popper": { "version": "2.3.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", + "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", "dependencies": { "react-fast-compare": "^3.0.1", "warning": "^4.0.2" @@ -13903,7 +14016,8 @@ }, "node_modules/react-tabs": { "version": "6.0.2", - "license": "MIT", + "resolved": "https://registry.npmjs.org/react-tabs/-/react-tabs-6.0.2.tgz", + "integrity": "sha512-aQXTKolnM28k3KguGDBSAbJvcowOQr23A+CUJdzJtOSDOtTwzEaJA+1U4KwhNL9+Obe+jFS7geuvA7ICQPXOnQ==", "dependencies": { "clsx": "^2.0.0", "prop-types": "^15.5.0" @@ -13914,14 +14028,16 @@ }, "node_modules/react-tabs/node_modules/clsx": { "version": "2.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", "engines": { "node": ">=6" } }, "node_modules/react-tooltip": { "version": "5.21.5", - "license": "MIT", + "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.21.5.tgz", + "integrity": "sha512-ey70qf6pBGi4U6xpyNlZAHobAhlo2dfxmImR2Bzd/DbLTsAYWz3TEaK+RMFuUZMq6hSPRbUHQSkP2rHBq4uFVg==", "dependencies": { "@floating-ui/dom": "^1.0.0", "classnames": "^2.3.0" @@ -16069,7 +16185,8 @@ }, "node_modules/warning": { "version": "4.0.3", - "license": "MIT", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", "dependencies": { "loose-envify": "^1.0.0" } diff --git a/pwa/package.json b/pwa/package.json index eb7eb869..6d6751db 100644 --- a/pwa/package.json +++ b/pwa/package.json @@ -4,7 +4,9 @@ "private": true, "description": "Product Website Template", "author": "Conduction", - "keywords": ["gatsby"], + "keywords": [ + "gatsby" + ], "scripts": { "develop": "gatsby develop", "start": "gatsby develop", @@ -22,8 +24,8 @@ "prepare": "cd .. && husky install" }, "dependencies": { - "@conduction/components": "2.2.17", - "@conduction/theme": "1.0.48", + "@conduction/components": "2.2.18", + "@conduction/theme": "1.0.50", "@fortawesome/fontawesome-svg-core": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^6.4.0", "@fortawesome/react-fontawesome": "^0.1.18", @@ -55,9 +57,10 @@ "@nl-design-system-unstable/zwolle-design-tokens": "^1.0.0-alpha.100", "@parcel/watcher": "^2.3.0", "@tabler/icons-react": "2.21.0", + "@types/qs": "^6.9.9", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", - "@utrecht/component-library-react": "^1.0.0-alpha.355", + "@utrecht/component-library-react": "^1.0.0-alpha.394", "@utrecht/design-tokens": "^1.0.0-alpha.524", "axios": "^0.25.0", "clsx": "^1.1.1", @@ -70,6 +73,7 @@ "i18next": "^21.6.16", "jwt-decode": "^3.1.2", "lodash": "^4.17.21", + "qs": "^6.11.2", "react": "^18.2.0", "react-collapsible": "^2.10.0", "react-dom": "^18.2.0", diff --git a/pwa/src/apiService/resources/openWoo.ts b/pwa/src/apiService/resources/openWoo.ts index 91174196..4a2f178f 100644 --- a/pwa/src/apiService/resources/openWoo.ts +++ b/pwa/src/apiService/resources/openWoo.ts @@ -14,8 +14,10 @@ export default class OpenWoo { this._send = send; } - public getAll = async (filters: IFiltersContext, currentPage: number): Promise => { - let endpoint = `/openWOO?extend[]=all${filtersToQueryParams(filters)}&_order[Publicatiedatum]=desc&_limit=${OPEN_WOO_LIMIT}&_page=${currentPage}`; + public getAll = async (filters: IFiltersContext, currentPage: number, limit: number): Promise => { + let endpoint = `/openWOO?extend[]=all${filtersToQueryParams( + filters, + )}&_order[Publicatiedatum]=desc&_limit=${limit}&_page=${currentPage}`; if (process.env.GATSBY_OIDN_NUMBER) { endpoint += `&oidn=${process.env.GATSBY_OIDN_NUMBER}`; diff --git a/pwa/src/components/paginationLimitSelect/PaginationLimitSelect.module.css b/pwa/src/components/paginationLimitSelect/PaginationLimitSelect.module.css new file mode 100644 index 00000000..4357e4cd --- /dev/null +++ b/pwa/src/components/paginationLimitSelect/PaginationLimitSelect.module.css @@ -0,0 +1,12 @@ +.container { + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; + list-style-type: none; + + padding: 0; + margin: 0; + + user-select: none; +} diff --git a/pwa/src/components/paginationLimitSelect/PaginationLimitSelect.tsx b/pwa/src/components/paginationLimitSelect/PaginationLimitSelect.tsx new file mode 100644 index 00000000..5e10b88e --- /dev/null +++ b/pwa/src/components/paginationLimitSelect/PaginationLimitSelect.tsx @@ -0,0 +1,74 @@ +import * as React from "react"; +import * as styles from "./PaginationLimitSelect.module.css"; +import clsx from "clsx"; +import { useForm } from "react-hook-form"; +import { SelectSingle } from "@conduction/components"; +import { IQueryLimitContext, QUERY_LIMIT_DEFAULT, useQueryLimitContext } from "../../context/queryLimit"; +import { useTranslation } from "react-i18next"; + +interface PaginationLimitSelectProps { + queryLimitName: string; + layoutClassName?: string; +} + +export const PaginationLimitSelectComponent: React.FC = ({ + queryLimitName, + layoutClassName, +}) => { + const { + watch, + register, + control, + setValue, + formState: { errors }, + } = useForm(); + const { queryLimit, setQueryLimit } = useQueryLimitContext(); + const { t } = useTranslation(); + + const watchLimit = watch("limit"); + + const value = queryLimit[queryLimitName as keyof IQueryLimitContext]; + + React.useEffect(() => { + if (!watchLimit) return; + if (parseInt(watchLimit.value) === value) return; + + const selectedLimit = limitSelectOptions.find((limitOption) => limitOption.value === watchLimit.value); + + if (selectedLimit) { + setQueryLimit({ ...queryLimit, [queryLimitName]: parseInt(selectedLimit.value) }); + } + }, [watchLimit]); + + React.useEffect(() => { + setValue( + "limit", + limitSelectOptions.find((limitOption) => limitOption.value === (value !== undefined && value.toString())), + ); + }, []); + + return ( +
+ {t("Results per page")}: + +
+ ); +}; + +const limitSelectOptions = [ + { label: "6", value: "6" }, + { label: "9", value: "9" }, + { label: "12", value: "12" }, + { label: "21", value: "21" }, + { label: "30", value: "30" }, + { label: "60", value: "60" }, + { label: "120", value: "120" }, +]; diff --git a/pwa/src/context/global.ts b/pwa/src/context/global.ts index c9c3f4b8..189ac629 100644 --- a/pwa/src/context/global.ts +++ b/pwa/src/context/global.ts @@ -3,6 +3,7 @@ import { defaultGatsbyContext, IGatsbyContext } from "./gatsby"; import { defaultFiltersContext, IFiltersContext } from "./filters"; import { defaultDisplayContext, IDisplayContext } from "./displays"; import { defaultPaginationContext, IPaginationContext } from "./pagination"; +import { defaultQueryLimitContext, IQueryLimitContext } from "./queryLimit"; export interface IGlobalContext { initiated: boolean; @@ -10,6 +11,7 @@ export interface IGlobalContext { filters: IFiltersContext; displays: IDisplayContext; pagination: IPaginationContext; + queryLimit: IQueryLimitContext; } export const defaultGlobalContext: IGlobalContext = { @@ -18,6 +20,7 @@ export const defaultGlobalContext: IGlobalContext = { filters: defaultFiltersContext, displays: defaultDisplayContext, pagination: defaultPaginationContext, + queryLimit: defaultQueryLimitContext, }; export const GlobalContext = React.createContext< diff --git a/pwa/src/context/queryLimit.ts b/pwa/src/context/queryLimit.ts new file mode 100644 index 00000000..164c5eac --- /dev/null +++ b/pwa/src/context/queryLimit.ts @@ -0,0 +1,24 @@ +import * as React from "react"; +import { GlobalContext } from "./global"; + +export const QUERY_LIMIT_DEFAULT = 12; + +export interface IQueryLimitContext { + openWooObjectsQueryLimit: number; +} + +export const defaultQueryLimitContext: IQueryLimitContext = { + openWooObjectsQueryLimit: QUERY_LIMIT_DEFAULT, +}; + +export const useQueryLimitContext = () => { + const [globalContext, setGlobalContext] = React.useContext(GlobalContext); + + const queryLimit: IQueryLimitContext = globalContext.queryLimit; + + const setQueryLimit = (query: IQueryLimitContext) => { + setGlobalContext((context) => ({ ...context, queryLimit: { ...globalContext.queryLimit, ...query } })); + }; + + return { setQueryLimit, queryLimit }; +}; diff --git a/pwa/src/hooks/openWoo.ts b/pwa/src/hooks/openWoo.ts index e3f7d642..11d6bb03 100644 --- a/pwa/src/hooks/openWoo.ts +++ b/pwa/src/hooks/openWoo.ts @@ -7,8 +7,8 @@ import { IFiltersContext } from "../context/filters"; export const useOpenWoo = (queryClient: QueryClient) => { const API: APIService | null = React.useContext(APIContext); - const getAll = (filters: IFiltersContext, currentPage: number) => - useQuery(["OpenWoo", filters, currentPage], () => API?.OpenWoo.getAll(filters, currentPage), { + const getAll = (filters: IFiltersContext, currentPage: number, limit: number) => + useQuery(["OpenWoo", filters, currentPage, limit], () => API?.OpenWoo.getAll(filters, currentPage, limit), { onError: (error) => { console.warn(error.message); }, diff --git a/pwa/src/services/filtersToQueryParams.ts b/pwa/src/services/filtersToQueryParams.ts index 72df8ae5..8a15c6f3 100644 --- a/pwa/src/services/filtersToQueryParams.ts +++ b/pwa/src/services/filtersToQueryParams.ts @@ -1,31 +1,43 @@ export const filtersToQueryParams = (filters: any): string => { - Object.keys(filters) - .filter((key) => filterKeysToRemove.includes(key)) - .forEach((key) => { - delete filters[key]; - }); + const cleanedFilters = Object.fromEntries( + Object.entries(filters).filter(([key]) => !filterKeysToRemove.includes(key)), + ); - let params = ""; + const params = Object.entries(cleanedFilters) + .map(([key, value]) => { + if (!value) return null; - for (const [key, value] of Object.entries(filters)) { - if (!value) continue; + const formattedValue = Array.isArray(value) + ? value.map((v: string) => v.replace(/\s+/g, "_")).join(`&${key}[]=`) + : (value as string).replace(/\s+/g, "_"); - if (typeof value === "string") { - params += `&${key}=${value}`; - } + return `${Array.isArray(value) ? `${key}[]` : key}=${formattedValue}`; + }) + .filter(Boolean) + .join("&"); - if (Array.isArray(value)) { - let arrayParams = ""; + return params ? `&${params}` : ""; +}; + +export const filtersToUrlQueryParams = (filters: Record): string => { + const cleanedFilters = Object.fromEntries( + Object.entries(filters).filter(([key]) => !filterKeysToRemove.includes(key)), + ); + + const params = Object.entries(cleanedFilters) + .map(([key, value]) => { + if (!value) return null; - value.forEach((value) => { - arrayParams += `&${key}[]=${value}`; - }); + const formattedValue = Array.isArray(value) + ? value.map((v: string) => v.replace(/\s+/g, "_")).join(`&${key}[]=`) + : (value as string).replace(/\s+/g, "_"); - params += arrayParams; - } - } + return `${Array.isArray(value) ? `${key}[]` : key}=${formattedValue}`; + }) + .filter(Boolean) + .join("&"); - return params; + return params ? `?${params}` : ""; }; const filterKeysToRemove: string[] = []; diff --git a/pwa/src/templates/landing/LandingTemplate.module.css b/pwa/src/templates/landing/LandingTemplate.module.css index 48669c37..ddb02a7a 100644 --- a/pwa/src/templates/landing/LandingTemplate.module.css +++ b/pwa/src/templates/landing/LandingTemplate.module.css @@ -6,7 +6,7 @@ margin-block-end: var(--utrecht-space-block-lg); } -.header1 { - margin-block-start: 0px; - margin-block-end: 0px; +.pagination { + display: flex; + justify-content: space-between; } diff --git a/pwa/src/templates/landing/LandingTemplate.tsx b/pwa/src/templates/landing/LandingTemplate.tsx index fd8e27be..195fb473 100644 --- a/pwa/src/templates/landing/LandingTemplate.tsx +++ b/pwa/src/templates/landing/LandingTemplate.tsx @@ -11,18 +11,24 @@ import { QueryClient } from "react-query"; import { Pagination } from "@conduction/components"; import { usePaginationContext } from "../../context/pagination"; import { useTranslation } from "react-i18next"; +import { useQueryLimitContext } from "../../context/queryLimit"; +import { PaginationLimitSelectComponent } from "../../components/paginationLimitSelect/PaginationLimitSelect"; export const LandingTemplate: React.FC = () => { const { currentPage, setCurrentPage } = usePaginationContext(); const { filters } = useFiltersContext(); const { t } = useTranslation(); + const { queryLimit } = useQueryLimitContext(); const queryClient = new QueryClient(); - const getItems = useOpenWoo(queryClient).getAll(filters, currentPage); + const getItems = useOpenWoo(queryClient).getAll(filters, currentPage, queryLimit.openWooObjectsQueryLimit); + + React.useEffect(() => { + setCurrentPage(1); + }, [queryLimit.openWooObjectsQueryLimit]); return ( <> -

@@ -33,12 +39,14 @@ export const LandingTemplate: React.FC = () => { {getItems.data?.results && getItems.data?.results?.length > 0 && (
- - +
+ + +
)} {getItems.isLoading && } diff --git a/pwa/src/templates/templateParts/filters/FiltersTemplate.tsx b/pwa/src/templates/templateParts/filters/FiltersTemplate.tsx index 0cba6fd1..34dcc340 100644 --- a/pwa/src/templates/templateParts/filters/FiltersTemplate.tsx +++ b/pwa/src/templates/templateParts/filters/FiltersTemplate.tsx @@ -1,23 +1,30 @@ import * as React from "react"; import * as styles from "./FiltersTemplate.module.css"; import ResultsDisplaySwitch from "../../../components/resultsDisplaySwitch/ResultsDisplaySwitch"; +import _ from "lodash"; +import qs from "qs"; import { useForm } from "react-hook-form"; import { InputText, SelectSingle } from "@conduction/components"; -import { useFiltersContext } from "../../../context/filters"; +import { IFiltersContext, defaultFiltersContext, useFiltersContext } from "../../../context/filters"; import { Button } from "@utrecht/component-library-react/dist/css-module"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faMagnifyingGlass, faSpinner } from "@fortawesome/free-solid-svg-icons"; import { generateYearsArray } from "../../../data/years"; import { TEMP_PUBLICATION_TYPES } from "../../../data/PublicationType"; import { useTranslation } from "react-i18next"; +import { filtersToUrlQueryParams } from "../../../services/filtersToQueryParams"; +import { navigate } from "gatsby"; +import { useGatsbyContext } from "../../../context/gatsby"; interface FiltersTemplateProps { isLoading: boolean; } export const FiltersTemplate: React.FC = ({ isLoading }) => { - const { filters, setFilters } = useFiltersContext(); const { t } = useTranslation(); + const { filters, setFilters } = useFiltersContext(); + const { gatsbyContext } = useGatsbyContext(); + const [queryParams, setQueryParams] = React.useState(defaultFiltersContext); const filterTimeout = React.useRef(null); const { @@ -25,6 +32,7 @@ export const FiltersTemplate: React.FC = ({ isLoading }) = register, handleSubmit, watch, + setValue, formState: { errors }, } = useForm(); @@ -33,9 +41,30 @@ export const FiltersTemplate: React.FC = ({ isLoading }) = const today = new Date(); const currentYear = today.getFullYear(); + const url = gatsbyContext.location.search; + const [, params] = url.split("?"); + const parsedParams = qs.parse(params); + + const handleSetFormValues = (params: any): void => { + const basicFields: string[] = ["_search", "category"]; + basicFields.forEach((field) => setValue(field, params[field])); + + setValue( + "year", + generateYearsArray(currentYear - 1995).find((year: any) => { + return year.after === params.Publicatiedatum?.after && year.before === params.Publicatiedatum?.before; + }), + ); + + setValue( + "category", + TEMP_PUBLICATION_TYPES.find((option) => option.value === params.Categorie?.replace(/_/g, " ")), + ); + }; + const onSubmit = (data: any) => { setFilters({ - _search: data.title, + _search: data._search, "Publicatiedatum[after]": data.year?.after, "Publicatiedatum[before]": data.year?.before, Categorie: data.category?.value, @@ -48,24 +77,45 @@ export const FiltersTemplate: React.FC = ({ isLoading }) = filterTimeout.current = setTimeout(() => onSubmit(watcher), 500); }, [watcher]); + React.useEffect(() => { + if (_.isEmpty(parsedParams)) return; + + handleSetFormValues(parsedParams); + }, []); + + React.useEffect(() => { + //Prevents loop that puts user at top of page after scroll + if (_.isEqual(filters, queryParams)) return; + + setQueryParams(filters); + navigate(`/${filtersToUrlQueryParams(filters)}`); + }, [filters]); + return (
+ { + return ( + year.after === filters["Publicatiedatum[after]"] && year.before === filters["Publicatiedatum[before]"] + ); + })} {...{ register, errors, control }} ariaLabel={t("Select year")} /> + = ({ requ const { t, i18n } = useTranslation(); return ( - - - - {t("Subject")} - {t("Publication date")} - {t("Summary")} - - - - {requests.map((request) => ( - navigate(request.id)} - tabIndex={0} - aria-label={`${request.Titel}, ${ - request.Publicatiedatum ? translateDate(i18n.language, request.Publicatiedatum) : t("N/A") - }, ${request.Samenvatting}`} - > - {request.Titel ?? t("No subject available")} - - {request.Publicatiedatum - ? translateDate(i18n.language, request.Publicatiedatum) - : t("No publication date available")} - - {request.Samenvatting ?? t("No summary available")} + +
+ + + {t("Subject")} + {t("Publication date")} + {t("Summary")} - ))} - -
+ + + {requests.map((request) => ( + navigate(request.id)} + tabIndex={0} + aria-label={`${request.Titel}, ${ + request.Publicatiedatum ? translateDate(i18n.language, request.Publicatiedatum) : t("N/A") + }, ${request.Samenvatting}`} + > + {request.Titel ?? t("No subject available")} + + {request.Publicatiedatum + ? translateDate(i18n.language, request.Publicatiedatum) + : t("No publication date available")} + + {request.Samenvatting ?? t("No summary available")} + + ))} + + + ); }; diff --git a/pwa/src/translations/en.ts b/pwa/src/translations/en.ts index 1570bfbd..9f4620f1 100644 --- a/pwa/src/translations/en.ts +++ b/pwa/src/translations/en.ts @@ -14,6 +14,7 @@ export const en = { Address: "Address", Page: "Page", Jumbotron: "Jumbotron", + Limit: "Limit", "Jumbotron card": "Jumbotron card", "N/A": "N/A", "Details page": "Details page", @@ -51,4 +52,8 @@ export const en = { "Can open a new window": "Can open a new window", "No results found": "No results found", "Enter search query": "Enter search query", + "Results per page": "Results per page", + "Select result limit": "Select result limit", + "Scroll table to the left": "Scroll table to the left", + "Scroll table to the right": "Scroll table to the right", }; diff --git a/pwa/src/translations/nl.ts b/pwa/src/translations/nl.ts index 49eae986..1df1e1cb 100644 --- a/pwa/src/translations/nl.ts +++ b/pwa/src/translations/nl.ts @@ -14,6 +14,7 @@ export const nl = { Address: "Adres", Page: "Pagina", Jumbotron: "Jumbotron", + Limit: "Limiet", "Jumbotron card": "Jumbotron tegel", "N/A": "N.v.t", "Details page": "Detailpagina", @@ -51,4 +52,8 @@ export const nl = { "Can open a new window": "Kan een nieuw venster openen", "No results found": "Geen resultaten gevonden", "Enter search query": "Voer zoekopdracht in", + "Results per page": "Resultaten per pagina", + "Select result limit": "Selecteer resultaten limiet", + "Scroll table to the left": "Scroll tabel naar links", + "Scroll table to the right": "Scroll tabel naar rechts", };