Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/api/inclusion-numerique/inclusion-numerique.fetch-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ export const inclusionNumeriqueFetchApi = async <TRoute extends InclusionNumeriq
baseUrl: 'https://api.inclusion-numerique.anct.gouv.fr',
revalidate: 21600,
token: env.INCLUSION_NUMERIQUE_API_TOKEN
})(route, options ? toQueryParams(options) : undefined);
})(
route,
options
? toQueryParams(options, {
order: '.',
select: ','
})
: undefined
);

if (!res.ok) throw new Error('Failed to fetch data');

Expand Down
5 changes: 3 additions & 2 deletions src/api/inclusion-numerique/routes/lieux.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { FilterOptions, PaginateOptions, SelectOptions } from '@/libraries/api/options';
import type { FilterOptions, OrderOptions, PaginateOptions, SelectOptions } from '@/libraries/api/options';

export const LIEUX_ROUTE = 'carto' as const;

export type LieuxRouteOptions = PaginateOptions &
SelectOptions<LieuxRouteResponse[number]> &
FilterOptions<LieuxRouteResponse[number]>;
FilterOptions<LieuxRouteResponse[number]> &
OrderOptions<LieuxRouteResponse[number]>;

export type LieuxRouteResponse = {
id: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { type Region, regionMatchingDepartement, regionMatchingSlug } from '@/fe
import regions from '@/features/collectivites-territoriales/regions.json';
import { LieuxPage } from '@/features/lieux-inclusion-numerique/lieux.page';
import { appPageTitle } from '@/libraries/utils';
import { pageSchema } from '@/libraries/utils/page.schema';

export const generateMetadata = async ({ params }: { params: Promise<{ departement: string }> }): Promise<Metadata> => {
const slug: string = (await params).departement;
Expand All @@ -37,33 +38,43 @@ export const generateStaticParams = () =>
};
});

const Page = async ({ params }: { params: Promise<{ region: string; departement: string }> }) => {
type PageProps = {
params: Promise<{ region: string; departement: string }>;
searchParams?: Promise<{ page: string }>;
};

const Page = async ({ params, searchParams }: PageProps) => {
const regionSlug: string = (await params).region;
const departementSlug: string = (await params).departement;
const curentPage = pageSchema.parse((await searchParams)?.page);
const limit = 24;

const region: Region | undefined = regions.find(regionMatchingSlug(regionSlug));
const departement: Departement | undefined = departements.find(departementMatchingSlug(departementSlug));

if (!region || !departement) return notFound();

const lieux = await inclusionNumeriqueFetchApi(LIEUX_ROUTE, {
paginate: { limit: 24, offset: 0 },
paginate: { limit, offset: (curentPage - 1) * limit },
select: LIEU_LIST_FIELDS,
filter: { code_insee: `like.${departement.code}%` }
filter: { code_insee: `like.${departement.code}%` },
order: ['nom', 'asc']
});

const departementRouteResponse = await inclusionNumeriqueFetchApi(DEPARTEMENTS_ROUTE);

return (
<LieuxPage
totalLieux={departementRouteResponse.find(departementMatchingCode(departement.code))?.nombre_lieux ?? 0}
pageSize={limit}
curentPage={curentPage}
lieux={lieux.map((lieu) => toLieuListItem(new Date())(appendCollectivites(lieu)))}
breadcrumbsItems={[
{ label: 'France', href: '/' },
{ label: region.nom, href: `/${region.slug}` },
{ label: departement.nom }
]}
mapHref={`/${region.slug}/${departement.slug}`}
href={`/${region.slug}/${departement.slug}`}
/>
);
};
Expand Down
19 changes: 15 additions & 4 deletions src/app/(full-page)/(regions)/[region]/lieux/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { matchingRegionCode, type Region, regionMatchingSlug } from '@/features/
import regions from '@/features/collectivites-territoriales/regions.json';
import { LieuxPage } from '@/features/lieux-inclusion-numerique/lieux.page';
import { appPageTitle } from '@/libraries/utils';
import { pageSchema } from '@/libraries/utils/page.schema';

export const generateMetadata = async ({ params }: { params: Promise<{ region: string }> }): Promise<Metadata> => {
const slug: string = (await params).region;
Expand All @@ -22,26 +23,36 @@ export const generateMetadata = async ({ params }: { params: Promise<{ region: s

export const generateStaticParams = async () => regions.map(({ slug }: Region) => ({ region: slug }));

const Page = async ({ params }: { params: Promise<{ region: string }> }) => {
type PageProps = {
params: Promise<{ region: string }>;
searchParams?: Promise<{ page: string }>;
};

const Page = async ({ params, searchParams }: PageProps) => {
const slug: string = (await params).region;
const region: Region | undefined = regions.find(regionMatchingSlug(slug));
const curentPage = pageSchema.parse((await searchParams)?.page);
const limit = 24;

if (!region) return notFound();

const lieux = await inclusionNumeriqueFetchApi(LIEUX_ROUTE, {
paginate: { limit: 24, offset: 0 },
paginate: { limit, offset: (curentPage - 1) * limit },
select: LIEU_LIST_FIELDS,
filter: { or: `(${region.departements.map((code) => `code_insee.like.${code}%`).join(',')})` }
filter: { or: `(${region.departements.map((code) => `code_insee.like.${code}%`).join(',')})` },
order: ['nom', 'asc']
});

const regionRouteResponse = await inclusionNumeriqueFetchApi(REGIONS_ROUTE);

return (
<LieuxPage
totalLieux={regionRouteResponse.find(matchingRegionCode(region))?.nombre_lieux ?? 0}
pageSize={limit}
curentPage={curentPage}
lieux={lieux.map((lieu) => toLieuListItem(new Date())(appendCollectivites(lieu)))}
breadcrumbsItems={[{ label: 'France', href: '/' }, { label: region.nom }]}
mapHref={`/${region.slug}`}
href={`/${region.slug}`}
/>
);
};
Expand Down
19 changes: 15 additions & 4 deletions src/app/(full-page)/(regions)/lieux/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,36 @@ import { totalLieux } from '@/api/inclusion-numerique/transfer/total-lieux';
import { appendCollectivites } from '@/features/collectivites-territoriales/append-collectivites';
import { LieuxPage } from '@/features/lieux-inclusion-numerique/lieux.page';
import { appPageTitle } from '@/libraries/utils';
import { pageSchema } from '@/libraries/utils/page.schema';

export const generateMetadata = async (): Promise<Metadata> => ({
title: appPageTitle('Liste des lieux', 'France'),
description: "Consultez la liste de tous les lieux d'inclusion numérique de France."
});

const Page = async () => {
type PageProps = {
searchParams?: Promise<{ page: string }>;
};

const Page = async ({ searchParams }: PageProps) => {
const curentPage = pageSchema.parse((await searchParams)?.page);
const limit = 24;

const regionRouteResponse = await inclusionNumeriqueFetchApi(REGIONS_ROUTE);

const lieux = await inclusionNumeriqueFetchApi(LIEUX_ROUTE, {
paginate: { limit: 24, offset: 0 },
select: LIEU_LIST_FIELDS
paginate: { limit, offset: (curentPage - 1) * limit },
select: LIEU_LIST_FIELDS,
order: ['nom', 'asc']
});

return (
<LieuxPage
totalLieux={totalLieux(regionRouteResponse)}
pageSize={limit}
curentPage={curentPage}
lieux={lieux.map((lieu) => toLieuListItem(new Date())(appendCollectivites(lieu)))}
mapHref='/'
href='/'
/>
);
};
Expand Down
19 changes: 15 additions & 4 deletions src/app/(with-map)/(regions)/[region]/[departement]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { type Region, regionMatchingDepartement, regionMatchingSlug } from '@/fe
import regions from '@/features/collectivites-territoriales/regions.json';
import { DepartementLieuxPage } from '@/features/lieux-inclusion-numerique/departement-lieux.page';
import { appPageTitle } from '@/libraries/utils';
import { pageSchema } from '@/libraries/utils/page.schema';

export const generateMetadata = async ({ params }: { params: Promise<{ departement: string }> }): Promise<Metadata> => {
const slug: string = (await params).departement;
Expand All @@ -37,27 +38,37 @@ export const generateStaticParams = () =>
};
});

const Page = async ({ params }: { params: Promise<{ region: string; departement: string }> }) => {
type PageProps = {
params: Promise<{ region: string; departement: string }>;
searchParams?: Promise<{ page: string }>;
};

const Page = async ({ params, searchParams }: PageProps) => {
const regionSlug: string = (await params).region;
const departementSlug: string = (await params).departement;
const curentPage = pageSchema.parse((await searchParams)?.page);
const limit = 10;

const region: Region | undefined = regions.find(regionMatchingSlug(regionSlug));
const departement: Departement | undefined = departements.find(departementMatchingSlug(departementSlug));

if (!region || !departement) return notFound();

const lieux = await inclusionNumeriqueFetchApi(LIEUX_ROUTE, {
paginate: { limit: 10, offset: 0 },
paginate: { limit, offset: (curentPage - 1) * limit },
select: LIEU_LIST_FIELDS,
filter: { code_insee: `like.${departement.code}%` }
filter: { code_insee: `like.${departement.code}%` },
order: ['nom', 'asc']
});

const departementRouteResponse = await inclusionNumeriqueFetchApi(DEPARTEMENTS_ROUTE);

return (
<DepartementLieuxPage
lieux={lieux.map((lieu) => toLieuListItem(new Date())(appendCollectivites(lieu)))}
totalLieux={departementRouteResponse.find(departementMatchingCode(departement.code))?.nombre_lieux ?? 0}
pageSize={limit}
curentPage={curentPage}
lieux={lieux.map((lieu) => toLieuListItem(new Date())(appendCollectivites(lieu)))}
region={region}
departement={departement}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { regionMatchingDepartement } from './region';
import regions from './regions.json';

export const appendCollectivites = <T extends { code_insee: string }>(lieu: T) => {
const code: string = lieu.code_insee.startsWith('97') ? lieu.code_insee.slice(0, 3) : lieu.code_insee.slice(0, 2);
const code: string = lieu.code_insee?.startsWith('97') ? lieu.code_insee?.slice(0, 3) : lieu.code_insee?.slice(0, 2);

return {
...lieu,
Expand Down
27 changes: 22 additions & 5 deletions src/features/lieux-inclusion-numerique/departement-lieux.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,27 @@ import { CARTOGRAPHIE_LIEUX_INCLUSION_NUMERIQUE_ID } from '@/features/cartograph
import type { Departement } from '@/features/collectivites-territoriales/departement';
import type { Region } from '@/features/collectivites-territoriales/region';
import { Breadcrumbs } from '@/libraries/ui/blocks/breadcrumbs';
import { NextPageLink, PageLink, PreviousPageLink } from '@/libraries/ui/blocks/pagination/page-link';
import { Pagination } from '@/libraries/ui/blocks/pagination/pagination';
import { contentId } from '@/libraries/ui/blocks/skip-links/skip-links';
import SkipLinksPortal from '@/libraries/ui/blocks/skip-links/skip-links-portal';
import type { LieuListItem } from './lieu-list-item';
import { LieuxList } from './lieux-list';

export const DepartementLieuxPage = ({
region,
departement,
totalLieux,
lieux
pageSize,
curentPage,
lieux,
region,
departement
}: {
region: Region;
departement: Departement;
totalLieux: number;
pageSize: number;
curentPage: number;
lieux: LieuListItem[];
region: Region;
departement: Departement;
}): ReactNode => {
const map = useMap()[CARTOGRAPHIE_LIEUX_INCLUSION_NUMERIQUE_ID];

Expand All @@ -41,6 +47,17 @@ export const DepartementLieuxPage = ({
<span className='sr-only'>{departement.nom}</span> {totalLieux} lieux trouvés
</h1>
<LieuxList lieux={lieux} className='flex flex-col gap-2' />
<div className='text-center mt-10'>
<Pagination
curentPage={curentPage}
itemsCount={totalLieux}
pageSize={pageSize}
siblingCount={1}
nav={{ previous: PreviousPageLink, next: NextPageLink }}
>
{PageLink}
</Pagination>
</div>
</main>
</>
);
Expand Down
27 changes: 24 additions & 3 deletions src/features/lieux-inclusion-numerique/lieux.page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { ReactNode } from 'react';
import { RiRoadMapLine } from 'react-icons/ri';
import { Breadcrumbs } from '@/libraries/ui/blocks/breadcrumbs';
import { NextPageLink, PageLink, PreviousPageLink } from '@/libraries/ui/blocks/pagination/page-link';
import { Pagination } from '@/libraries/ui/blocks/pagination/pagination';
import { contentId } from '@/libraries/ui/blocks/skip-links/skip-links';
import SkipLinksPortal from '@/libraries/ui/blocks/skip-links/skip-links-portal';
import { ButtonLink } from '@/libraries/ui/primitives/button-link';
Expand All @@ -9,24 +11,43 @@ import { LieuxList } from './lieux-list';

type LieuxPageProps = {
breadcrumbsItems?: { label: string; href?: string }[];
mapHref: string;
href: string;
lieux: LieuListItem[];
totalLieux: number;
curentPage: number;
pageSize: number;
};

export const LieuxPage = ({ breadcrumbsItems = [], mapHref, lieux, totalLieux }: LieuxPageProps): ReactNode => (
export const LieuxPage = ({
breadcrumbsItems = [],
href,
lieux,
totalLieux,
curentPage,
pageSize
}: LieuxPageProps): ReactNode => (
<>
<SkipLinksPortal />
<main id={contentId} className='container mx-auto px-4'>
<div className='py-6 flex justify-between align-center gap-4'>
<Breadcrumbs items={breadcrumbsItems} />
<ButtonLink color='btn-primary' href={mapHref} className='ml-auto'>
<ButtonLink color='btn-primary' href={href} className='ml-auto'>
<RiRoadMapLine aria-hidden={true} />
Afficher la carte
</ButtonLink>
</div>
<h1 className='font-bold uppercase text-xs text-base-title mb-6'>{totalLieux} lieux trouvés</h1>
<LieuxList lieux={lieux} size='lg' className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2' />
<div className='text-center mt-10'>
<Pagination
curentPage={curentPage}
itemsCount={totalLieux}
pageSize={pageSize}
nav={{ previous: PreviousPageLink, next: NextPageLink }}
>
{PageLink}
</Pagination>
</div>
</main>
</>
);
12 changes: 11 additions & 1 deletion src/libraries/api/options.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,18 @@ describe('options', () => {
select: ['id', 'name']
};

const queryParams = toQueryParams(options);
const queryParams = toQueryParams(options, { select: ',' });

expect(queryParams).toStrictEqual('select=id%2Cname');
});

it('should create query params for order option', () => {
const options = {
order: ['name', 'asc']
};

const queryParams = toQueryParams(options, { order: '.' });

expect(queryParams).toStrictEqual('order=name.asc');
});
});
16 changes: 12 additions & 4 deletions src/libraries/api/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,30 @@ export type FilterOptions<TItem> = {
filter?: Partial<Record<keyof TItem, unknown> | { or: string }>;
};

export type OrderOptions<TItem> = {
order?: [keyof TItem, 'desc' | 'asc'];
};

const toQueryParam = (urlSerachParams: URLSearchParams, [key, value]: [string, string]): URLSearchParams => {
if (value != null) urlSerachParams.set(key, value);
return urlSerachParams;
};

const toQueryArray = (urlSerachParams: URLSearchParams, [key, value]: [string, string[]]) => {
urlSerachParams.set(key, value.join(','));
const toQueryArray = <T extends Record<string, object>>(
urlSerachParams: URLSearchParams,
[key, value]: [string, string[]],
separators?: { [K in keyof T]?: string }
) => {
urlSerachParams.set(key, value.join(separators?.[key] ?? ','));
return urlSerachParams;
};

export const toQueryParams = (options: Record<string, object>): string =>
export const toQueryParams = <T extends Record<string, object>>(options: T, separators?: { [K in keyof T]?: string }): string =>
Object.entries(options)
.reduce(
(urlSerachParams: URLSearchParams, [key, value]) =>
Array.isArray(value)
? toQueryArray(urlSerachParams, [key, value])
? toQueryArray(urlSerachParams, [key, value], separators)
: Object.entries(value).reduce(toQueryParam, urlSerachParams),
new URLSearchParams()
)
Expand Down
Loading