diff --git a/packages/app/src/components/aside/menu.tsx b/packages/app/src/components/aside/menu.tsx
index 1cf045326d..6882bfa179 100644
--- a/packages/app/src/components/aside/menu.tsx
+++ b/packages/app/src/components/aside/menu.tsx
@@ -100,9 +100,10 @@ interface MenuItemLinkProps {
icon?: ReactNode;
href?: Url;
showArrow?: boolean;
+ isLinkForMainMenu?: boolean;
}
-export function MenuItemLink({ href, title }: MenuItemLinkProps) {
+export function MenuItemLink({ href, title, icon, isLinkForMainMenu = true }: MenuItemLinkProps) {
const router = useRouter();
const breakpoints = useBreakpoints(true);
@@ -121,8 +122,8 @@ export function MenuItemLink({ href, title }: MenuItemLinkProps) {
return (
-
-
+
+
@@ -166,10 +167,10 @@ const Unavailable = styled.span(
})
);
-const StyledAnchor = styled(Anchor)<{ isActive: boolean }>((anchorProps) =>
+const StyledAnchor = styled(Anchor)<{ isActive: boolean; isLinkForMainMenu: boolean }>((anchorProps) =>
css({
padding: space[2],
- paddingLeft: '3rem',
+ paddingLeft: anchorProps.isLinkForMainMenu ? '3rem' : '0.5rem',
display: 'block',
borderRight: '5px solid transparent',
color: anchorProps.isActive ? colors.blue8 : 'black',
diff --git a/packages/app/src/components/aside/title.tsx b/packages/app/src/components/aside/title.tsx
index 7f144ab1b6..5da8a9fdeb 100644
--- a/packages/app/src/components/aside/title.tsx
+++ b/packages/app/src/components/aside/title.tsx
@@ -5,6 +5,7 @@ import styled from 'styled-components';
import { space } from '~/style/theme';
import { Box } from '../base';
import { Text } from '../typography';
+import css from '@styled-system/css';
type TitleProps = {
title: string;
@@ -13,13 +14,17 @@ type TitleProps = {
showArrow?: boolean;
};
-export const AsideTitle = ({ title, subtitle, showArrow }: TitleProps) => {
+export const AsideTitle = ({ title, subtitle, showArrow, icon }: TitleProps) => {
return (
- {title}
+
+ {icon && {icon}}
+ {title}
+
+
{showArrow && }
@@ -36,3 +41,26 @@ const AsideArrow = styled(ChevronRight)`
height: 20px;
width: ${space[3]};
`;
+
+const Icon = ({ children }: { children: ReactNode }) => {
+ return (
+
+ {children}
+
+ );
+};
diff --git a/packages/app/src/components/layout/app-content.tsx b/packages/app/src/components/layout/app-content.tsx
index d2aed25adf..6196fb3de7 100644
--- a/packages/app/src/components/layout/app-content.tsx
+++ b/packages/app/src/components/layout/app-content.tsx
@@ -16,9 +16,10 @@ interface AppContentProps {
sidebarComponent: React.ReactNode;
searchComponent?: React.ReactNode;
hideBackButton?: boolean;
+ displayAsFlex?: boolean;
}
-export function AppContent({ children, sidebarComponent, searchComponent, hideBackButton }: AppContentProps) {
+export function AppContent({ children, sidebarComponent, searchComponent, hideBackButton, displayAsFlex = false }: AppContentProps) {
const router = useRouter();
const reverseRouter = useReverseRouter();
const { commonTexts } = useIntl();
@@ -28,7 +29,6 @@ export function AppContent({ children, sidebarComponent, searchComponent, hideBa
const currentCode = router.query.code as string | undefined;
const backButtonConfig = { currentPageScope, currentCode, isMenuOpen, reverseRouter, commonTexts };
const backButtonValues = getBackButtonValues(backButtonConfig);
-
return (
@@ -47,12 +47,12 @@ export function AppContent({ children, sidebarComponent, searchComponent, hideBa
flexShrink={0}
minHeight={{ lg: '35em' }}
width={{ md: '18rem', lg: '21rem' }}
+ display={displayAsFlex ? 'flex' : 'block'}
zIndex={3}
+ justifyContent="center"
>
-
- {searchComponent}
- {sidebarComponent}
-
+ {searchComponent}
+ {sidebarComponent}
{/* id is for hash navigation */}
@@ -114,3 +114,15 @@ const ResponsiveVisible = styled.div`
display: block;
}
`;
+
+const ResponsiveVisibleAside = styled.div`
+ display: ${({ isVisible }) => (isVisible ? 'flex' : 'none')};
+
+ @media ${mediaQueries.md} {
+ display: ${({ isVisible }) => (!isVisible ? 'flex' : undefined)};
+ }
+
+ .has-no-js & {
+ display: block;
+ }
+`;
diff --git a/packages/app/src/domain/layout/gm-layout.tsx b/packages/app/src/domain/layout/gm-layout.tsx
index 0929ecf76d..b523a46177 100644
--- a/packages/app/src/domain/layout/gm-layout.tsx
+++ b/packages/app/src/domain/layout/gm-layout.tsx
@@ -1,18 +1,24 @@
-import Head from 'next/head';
-import { useRouter } from 'next/router';
-import { Menu, MenuRenderer } from '~/components/aside/menu';
+import { AppContent } from '~/components/layout/app-content';
import { Box } from '~/components/base';
import { ErrorBoundary } from '~/components/error-boundary';
-import { AppContent } from '~/components/layout/app-content';
+import { GmComboBox } from './components/gm-combo-box';
import { Heading } from '~/components/typography';
-import { VisuallyHidden } from '~/components/visually-hidden';
-import { useIntl } from '~/intl';
+import { List } from '@corona-dashboard/icons';
+import { Menu, MenuItemLink, MenuRenderer } from '~/components/aside/menu';
import { space } from '~/style/theme';
-import { GmComboBox } from './components/gm-combo-box';
+import { useIntl } from '~/intl';
+import { useReverseRouter } from '~/utils';
+import { useRouter } from 'next/router';
import { useSidebar } from './logic/use-sidebar';
+import { VisuallyHidden } from '~/components/visually-hidden';
+import Head from 'next/head';
+import React from 'react';
type GmLayoutProps = {
children?: React.ReactNode;
+ asideComponent?: React.ReactNode;
+ displayListButton?: React.ReactNode;
+ displayAsFlex?: boolean;
getLink?: (code: string) => string;
} & (
| {
@@ -35,23 +41,50 @@ type GmLayoutProps = {
* ## States
*
* ### Mobile
- * - /gemeente -> only show aside
+ * - /gemeente && /gemeente/lijstweergave-> only show aside
* - /gemeente/[metric] -> only show content (children)
*
* ### Desktop
- * - /gemeente -> shows aside and content (children)
+ * - /gemeente && /gemeente/lijstweergave -> shows aside and content (children)
* - /gemeente/[metric] -> shows aside and content (children)
*
* More info on persistent layouts:
* https://adamwathan.me/2019/10/17/persistent-layout-patterns-in-nextjs/
*/
export function GmLayout(props: GmLayoutProps) {
- const { children, municipalityName, code, getLink } = props;
-
+ const reverseRouter = useReverseRouter();
const { commonTexts } = useIntl();
+
+ const {
+ children,
+ municipalityName,
+ code,
+ getLink,
+ displayListButton,
+ asideComponent = displayListButton ? (
+
+
+
+
+
+
+
+
+ ) : (
+
+
+
+
+
+ ),
+ displayAsFlex = false,
+ } = props;
+
const router = useRouter();
- const showMetricLinks = router.route !== '/gemeente';
+ const showMetricLinks = router.route !== '/gemeente' && router.route !== '/gemeente/lijstweergave';
const isMainRoute = router.route === '/gemeente';
@@ -74,11 +107,7 @@ export function GmLayout(props: GmLayoutProps) {
-
-
- }
+ searchComponent={asideComponent}
sidebarComponent={
<>
{showMetricLinks && (
@@ -107,6 +136,7 @@ export function GmLayout(props: GmLayoutProps) {
)}
>
}
+ displayAsFlex={displayAsFlex}
>
{children}
diff --git a/packages/app/src/next-config/rewrites.js b/packages/app/src/next-config/rewrites.js
index 13a5be3463..2741b150d5 100644
--- a/packages/app/src/next-config/rewrites.js
+++ b/packages/app/src/next-config/rewrites.js
@@ -19,7 +19,7 @@ async function rewrites() {
* 3. /gemeente/gblah
*/
{
- source: '/gemeente/((?!gm|GM|gM|Gm).*):slug*',
+ source: '/gemeente/((?!gm|GM|gM|Gm|lijstweergave).*):slug*',
destination: '/gemeente/code/404',
},
/**
diff --git a/packages/app/src/pages/gemeente/index.tsx b/packages/app/src/pages/gemeente/index.tsx
index ee402b4bac..e8fe25bffb 100644
--- a/packages/app/src/pages/gemeente/index.tsx
+++ b/packages/app/src/pages/gemeente/index.tsx
@@ -8,7 +8,9 @@ import { GmComboBox } from '~/domain/layout/components/gm-combo-box';
import { GmLayout } from '~/domain/layout/gm-layout';
import { Heading } from '~/components/typography';
import { Layout } from '~/domain/layout/layout';
+import { List } from '@corona-dashboard/icons';
import { Markdown } from '~/components/markdown';
+import { Menu, MenuItemLink } from '~/components/aside/menu';
import { space } from '~/style/theme';
import { TooltipContent } from '~/components/choropleth/tooltips';
import { useBreakpoints } from '~/utils/use-breakpoints';
@@ -44,7 +46,7 @@ const Municipality = (props: StaticProps) => {
return (
-
+
{!breakpoints.md && (
@@ -84,6 +86,12 @@ const Municipality = (props: StaticProps) => {
+
+ {!breakpoints.md && (
+
+ )}
);
diff --git a/packages/app/src/pages/gemeente/lijstweergave.tsx b/packages/app/src/pages/gemeente/lijstweergave.tsx
new file mode 100644
index 0000000000..c77041ae37
--- /dev/null
+++ b/packages/app/src/pages/gemeente/lijstweergave.tsx
@@ -0,0 +1,172 @@
+import _ from 'lodash';
+import { Box } from '~/components/base';
+import { CollapsibleSection } from '~/components';
+import { colors, gmData, MunicipalityInfo } from '@corona-dashboard/common';
+import { createGetStaticProps, StaticProps } from '~/static-props/create-get-static-props';
+import { getLastGeneratedDate } from '~/static-props/get-data';
+import { GmLayout, Layout } from '~/domain/layout';
+import { Heading } from '~/components/typography';
+import { Link } from '~/utils/link';
+import { Map } from '@corona-dashboard/icons';
+import { Menu, MenuItemLink } from '~/components/aside/menu';
+import { radii, space } from '~/style/theme';
+import { useBreakpoints } from '~/utils/use-breakpoints';
+import { useIntl } from '~/intl';
+import { useReverseRouter } from '~/utils';
+import { useRouter } from 'next/router';
+import React, { Fragment } from 'react';
+import styled from 'styled-components';
+
+export const getStaticProps = createGetStaticProps(getLastGeneratedDate);
+
+enum MunicipalityLetter {
+ A = 'A',
+ B = 'B',
+ C = 'C',
+ D = 'D',
+ E = 'E',
+ G = 'G',
+ H = 'H',
+ I = 'I',
+ K = 'K',
+ L = 'L',
+ M = 'M',
+ N = 'N',
+ O = 'O',
+ P = 'P',
+ R = 'R',
+ S = 'S',
+ T = 'T',
+ U = 'U',
+ V = 'V',
+ W = 'W',
+ Z = 'Z',
+}
+
+const MunicipalityListOverview = (props: StaticProps) => {
+ const { lastGenerated } = props;
+ const { commonTexts } = useIntl();
+ const reverseRouter = useReverseRouter();
+ const router = useRouter();
+ const breakpoints = useBreakpoints();
+
+ const sortedGmData = _.sortBy(gmData, [(municipality) => (municipality.displayName ? municipality.displayName : municipality.name)]);
+ const groupedGmData = _.groupBy(
+ sortedGmData,
+ (municipality) => (municipality.displayName ? municipality.displayName : municipality.name)[0].toUpperCase() as MunicipalityLetter
+ );
+
+ const code = router.query.code as string;
+
+ const metadata = {
+ ...commonTexts.gemeente_index.metadata,
+ };
+
+ return (
+
+
+
+
+
+
+ }
+ isLandingPage
+ code={code}
+ >
+
+ {commonTexts.gemeente_layout.list.list_label}
+
+ {Object.entries(MunicipalityLetter).map(([letterKey, letterValue], index) => (
+
+
+
+ {letterValue}
+
+
+
+ ))}
+
+
+ <>
+ {Object.entries(MunicipalityLetter).map(([letterKey, letterValue], index) => (
+
+ {!breakpoints.sm ? (
+
+
+ {groupedGmData[letterKey].map((item, index) => (
+
+
+
+ {item.name}
+
+
+
+ ))}
+
+
+ ) : (
+
+
+ {letterValue}
+
+
+
+ {groupedGmData[letterKey].map((item, index) => (
+
+
+
+ {item.displayName ? item.displayName : item.name}
+
+
+
+ ))}
+
+
+ )}
+
+ ))}
+ >
+
+
+
+ );
+};
+
+export default MunicipalityListOverview;
+
+const StyledLetter = styled(Box)`
+ text-decoration: underline;
+
+ width: 24px;
+ height: 24px;
+
+ a {
+ color: black;
+ }
+`;
+
+const StyledCollapsibleSection = styled(CollapsibleSection)`
+ border: 1px solid ${colors.blue8};
+ border-radius: ${radii[1]}px;
+ color: black;
+`;
+
+const StyledAnchor = styled(Box)`
+ text-decoration: underline;
+ text-decoration-color: ${colors.blue8};
+`;
+
+const ColumnContainer = styled(Box)`
+ column-count: 4;
+ column-gap: 20px;
+ width: 100%;
+`;
+const ColumnItem = styled(Box)`
+ break-inside: avoid-column;
+ page-break-inside: avoid;
+ padding: ${space[1]} 0;
+`;
diff --git a/packages/cms/src/lokalize/key-mutations.csv b/packages/cms/src/lokalize/key-mutations.csv
index d16c26e664..ace26655a5 100644
--- a/packages/cms/src/lokalize/key-mutations.csv
+++ b/packages/cms/src/lokalize/key-mutations.csv
@@ -1,4 +1,13 @@
timestamp,action,key,document_id,move_to
+2024-02-29T16:56:00.034Z,add,pages.municipality_list_page.gm.go_to_list_label,Nj97lpO3jQh76onlHCRsla,__
+2024-02-29T16:56:00.971Z,add,pages.municipality_list_page.gm.go_to_map_label,Nj97lpO3jQh76onlHCRt42,__
+2024-02-29T16:56:02.195Z,add,pages.municipality_list_page.gm.list_label,aLsER6e7bVv6SzRxzsorn7,__
+2024-02-29T17:08:19.417Z,add,common.gemeente_layout.list.go_to_list_label,jsGiCtokn2h16No2XOCjsg,__
+2024-02-29T17:08:21.363Z,add,common.gemeente_layout.list.go_to_map_label,aIhqVlpPHSWN4KBoRtCqN3,__
+2024-02-29T17:08:22.547Z,add,common.gemeente_layout.list.list_label,P56mtwcJXH3ayG8JPz9lUD,__
+2024-02-29T17:08:22.554Z,delete,pages.municipality_list_page.gm.go_to_list_label,Nj97lpO3jQh76onlHCRsla,__
+2024-02-29T17:08:22.556Z,delete,pages.municipality_list_page.gm.go_to_map_label,Nj97lpO3jQh76onlHCRt42,__
+2024-02-29T17:08:22.557Z,delete,pages.municipality_list_page.gm.list_label,aLsER6e7bVv6SzRxzsorn7,__
2024-02-22T14:50:50.064Z,add,common.common.metadata.metrics_json_links.metrics_json_source,BDRHgHFora2UCq5UG30sAv,__
2024-02-22T14:50:50.989Z,add,common.common.metadata.metrics_json_links.metrics_national_json.href,2z0g0X7Nweo2izzuiKMmnu,__
2024-02-22T14:50:51.987Z,add,common.common.metadata.metrics_json_links.metrics_national_json.text,BDRHgHFora2UCq5UG30sFf,__
diff --git a/packages/common/src/data/reverse-router.ts b/packages/common/src/data/reverse-router.ts
index 1f00c20599..43524c907f 100644
--- a/packages/common/src/data/reverse-router.ts
+++ b/packages/common/src/data/reverse-router.ts
@@ -44,6 +44,7 @@ export function getReverseRouter(isMobile: boolean) {
patientenInBeeld: (code: string) => `/gemeente/${code}/patienten-in-beeld`,
rioolwater: (code: string) => `/gemeente/${code}/rioolwater`,
deCoronaprik: (code: string) => `/gemeente/${code}/de-coronaprik`,
+ lijstweergave: () => '/gemeente/lijstweergave',
},
} as const;
diff --git a/packages/icons/icons.md b/packages/icons/icons.md
index b791afa664..ef5370ddea 100644
--- a/packages/icons/icons.md
+++ b/packages/icons/icons.md
@@ -76,10 +76,12 @@ See below an overview of all the available icons in this package. This file is g
| KunstCultuur | |
| KunstcultuurMusea | |
| Line | |
+| List | |
| Locaties | |
| Location | |
| Lopend | |
| Maatregelen | |
+| Map | |
| MaxAantalBezoekers | |
| MaxVisitors | |
| MedischeScreening | |
diff --git a/packages/icons/src/icon-name2filename.ts b/packages/icons/src/icon-name2filename.ts
index 307f71583c..31575d5691 100644
--- a/packages/icons/src/icon-name2filename.ts
+++ b/packages/icons/src/icon-name2filename.ts
@@ -71,10 +71,12 @@ export type IconName =
| 'KunstCultuur'
| 'KunstcultuurMusea'
| 'Line'
+ | 'List'
| 'Locaties'
| 'Location'
| 'Lopend'
| 'Maatregelen'
+ | 'Map'
| 'MaxAantalBezoekers'
| 'MaxVisitors'
| 'MedischeScreening'
@@ -211,10 +213,12 @@ export const iconName2filename: Record = {
KunstCultuur: 'kunst_cultuur.svg',
KunstcultuurMusea: 'kunstcultuur_musea.svg',
Line: 'line.svg',
+ List: 'list.svg',
Locaties: 'locaties.svg',
Location: 'location.svg',
Lopend: 'lopend.svg',
Maatregelen: 'maatregelen.svg',
+ Map: 'map.svg',
MaxAantalBezoekers: 'max_aantal_bezoekers.svg',
MaxVisitors: 'max_visitors.svg',
MedischeScreening: 'medische_screening.svg',
diff --git a/packages/icons/src/svg/list.svg b/packages/icons/src/svg/list.svg
new file mode 100644
index 0000000000..d07ca08e19
--- /dev/null
+++ b/packages/icons/src/svg/list.svg
@@ -0,0 +1,9 @@
+
diff --git a/packages/icons/src/svg/map.svg b/packages/icons/src/svg/map.svg
new file mode 100644
index 0000000000..1039dfa39b
--- /dev/null
+++ b/packages/icons/src/svg/map.svg
@@ -0,0 +1,11 @@
+