diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f62f41d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+# IDE
+.idea
\ No newline at end of file
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 0000000..c68b9ec
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1,2 @@
+cd ./cms && yarn config-sync export -y
+cd ../client && yarn types && yarn lint --fix && yarn check-types && git add src/types/generated/
\ No newline at end of file
diff --git a/client/.eslintignore b/client/.eslintignore
index d85a799..f458037 100644
--- a/client/.eslintignore
+++ b/client/.eslintignore
@@ -1,3 +1,4 @@
/.next
/node_modules
-/public
\ No newline at end of file
+/public
+/src/types/generated/
\ No newline at end of file
diff --git a/client/.eslintrc.json b/client/.eslintrc.json
index efff856..0d77ef9 100644
--- a/client/.eslintrc.json
+++ b/client/.eslintrc.json
@@ -3,6 +3,7 @@
"next/core-web-vitals",
"plugin:prettier/recommended",
"plugin:@typescript-eslint/recommended",
+ "plugin:@tanstack/query/recommended",
"plugin:import/recommended",
"plugin:import/typescript"
],
diff --git a/client/.gitignore b/client/.gitignore
index fd3dbb5..24c756b 100644
--- a/client/.gitignore
+++ b/client/.gitignore
@@ -34,3 +34,6 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
+
+# IDE
+.idea
diff --git a/client/package.json b/client/package.json
index 3736928..91cd124 100644
--- a/client/package.json
+++ b/client/package.json
@@ -3,10 +3,13 @@
"version": "0.1.0",
"private": true,
"scripts": {
+ "prepare": "cd .. && husky",
"dev": "next dev",
"build": "next build",
"start": "next start",
- "lint": "next lint"
+ "lint": "next lint",
+ "types": "orval --config ./src/orval.config.ts",
+ "check-types": "tsc"
},
"dependencies": {
"@artsy/fresnel": "7.1.4",
@@ -18,7 +21,9 @@
"@radix-ui/react-slot": "1.1.0",
"@radix-ui/react-tooltip": "1.1.3",
"@t3-oss/env-nextjs": "0.11.1",
+ "@tanstack/react-query": "5.59.16",
"@types/mapbox-gl": "3.4.0",
+ "axios": "1.7.7",
"class-variance-authority": "0.7.0",
"clsx": "2.1.1",
"express": "4.21.1",
@@ -36,6 +41,7 @@
},
"devDependencies": {
"@svgr/webpack": "8.1.0",
+ "@tanstack/eslint-plugin-query": "5.59.7",
"@types/node": "22.7.6",
"@types/react": "18.3.1",
"@types/react-dom": "18.3.1",
@@ -47,7 +53,10 @@
"eslint-import-resolver-typescript": "3.6.3",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-prettier": "5.2.1",
+ "husky": "9.1.6",
"jiti": "1.21.6",
+ "orval": "7.2.0",
+ "pinst": "3.0.0",
"postcss": "^8",
"prettier": "3.3.3",
"prettier-plugin-tailwindcss": "0.6.8",
diff --git a/client/src/app/layout.tsx b/client/src/app/layout.tsx
index 7f7934d..5910464 100644
--- a/client/src/app/layout.tsx
+++ b/client/src/app/layout.tsx
@@ -1,6 +1,7 @@
import { Jost, DM_Serif_Text } from "next/font/google";
import { NuqsAdapter } from "nuqs/adapters/next/app";
+import ReactQueryProvider from "@/app/react-query-provider";
import Head from "@/components/head";
import type { Metadata } from "next";
@@ -37,7 +38,9 @@ export default function RootLayout({
- {children}
+
+ {children}
+
);
diff --git a/client/src/app/react-query-provider.tsx b/client/src/app/react-query-provider.tsx
new file mode 100644
index 0000000..018c5e9
--- /dev/null
+++ b/client/src/app/react-query-provider.tsx
@@ -0,0 +1,43 @@
+"use client";
+
+// Since QueryClientProvider relies on useContext under the hood, we have to put 'use client' on top
+import { isServer, QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { PropsWithChildren } from "react";
+
+function makeQueryClient() {
+ return new QueryClient({
+ defaultOptions: {
+ queries: {
+ // With SSR, we usually want to set some default staleTime
+ // above 0 to avoid refetching immediately on the client
+ staleTime: 60 * 1000,
+ },
+ },
+ });
+}
+
+let browserQueryClient: QueryClient | undefined = undefined;
+
+function getQueryClient() {
+ if (isServer) {
+ // Server: always make a new query client
+ return makeQueryClient();
+ } else {
+ // Browser: make a new query client if we don't already have one
+ // This is very important, so we don't re-make a new client if React
+ // suspends during the initial render. This may not be needed if we
+ // have a suspense boundary BELOW the creation of the query client
+ if (!browserQueryClient) browserQueryClient = makeQueryClient();
+ return browserQueryClient;
+ }
+}
+
+export default function ReactQueryProvider({ children }: PropsWithChildren) {
+ // NOTE: Avoid useState when initializing the query client if you don't
+ // have a suspense boundary between this and the code that may
+ // suspend because React will throw away the client on the initial
+ // render if it suspends and there is no boundary
+ const queryClient = getQueryClient();
+
+ return {children} ;
+}
diff --git a/client/src/components/map/controls/contextual-layers.tsx b/client/src/components/map/controls/contextual-layers.tsx
new file mode 100644
index 0000000..0f5f9a5
--- /dev/null
+++ b/client/src/components/map/controls/contextual-layers.tsx
@@ -0,0 +1,26 @@
+import ContextualLayersPanel from "@/components/panels/contextual-layers";
+import { Button } from "@/components/ui/button";
+import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
+import LayersIcon from "@/svgs/layers.svg";
+
+const ContextualLayersControls = () => {
+ return (
+
+
+
+
+ Contextual layers
+
+
+
+
+
+
+ );
+};
+
+export default ContextualLayersControls;
diff --git a/client/src/components/map/controls/index.tsx b/client/src/components/map/controls/index.tsx
index 96a0091..fa77efe 100644
--- a/client/src/components/map/controls/index.tsx
+++ b/client/src/components/map/controls/index.tsx
@@ -1,12 +1,18 @@
+import ContextualLayersControls from "./contextual-layers";
import MapSettingsControls from "./map-settings";
import ZoomControls from "./zoom";
const Controls = () => {
return (
-
-
-
-
+ <>
+
+
+
+
+
+
+
+ >
);
};
diff --git a/client/src/components/map/controls/zoom.tsx b/client/src/components/map/controls/zoom.tsx
index e0e2745..924ac07 100644
--- a/client/src/components/map/controls/zoom.tsx
+++ b/client/src/components/map/controls/zoom.tsx
@@ -12,24 +12,12 @@ const ZoomControls = () => {
const onClickZoomOut = useCallback(() => map?.zoomOut(), [map]);
return (
-
-
+
+
Zoom in
-
+
Zoom out
diff --git a/client/src/components/panels/contextual-layers/index.tsx b/client/src/components/panels/contextual-layers/index.tsx
new file mode 100644
index 0000000..4dab797
--- /dev/null
+++ b/client/src/components/panels/contextual-layers/index.tsx
@@ -0,0 +1,81 @@
+"use client";
+
+import { Button, buttonVariants } from "@/components/ui/button";
+import { SheetClose, SheetHeader, SheetTitle } from "@/components/ui/sheet";
+import { Skeleton } from "@/components/ui/skeleton";
+import useDatasetsBySubTopic from "@/hooks/use-datasets-by-sub-topic";
+import XMarkIcon from "@/svgs/xmark.svg";
+
+import Item from "./item";
+
+const ContextualLayersPanel = () => {
+ const { data, isLoading } = useDatasetsBySubTopic("contextual");
+
+ return (
+ <>
+
+
+ Contextual layers
+
+
+
+
+ Close
+
+ Contextual layers
+
+
+
+
+
+
+ Introduction lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit
+ amet, consectetu elit.
+
+
+ {isLoading && (
+ <>
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+ {!isLoading && data.length === 0 && (
+ Data not available
+ )}
+
+ {!isLoading && data.length > 0 && (
+
+ {data.map(({ subTopic, datasets }) => (
+ - dataset.layers.length > 0)
+ .map((dataset) => ({
+ // Assuming the dataset has just one layer, which is currently the case
+ id: dataset.layers[0].id!,
+ name: dataset.layers[0].attributes!.name!,
+ }))}
+ />
+ ))}
+
+ )}
+ >
+ );
+};
+
+export default ContextualLayersPanel;
diff --git a/client/src/components/panels/contextual-layers/item.tsx b/client/src/components/panels/contextual-layers/item.tsx
new file mode 100644
index 0000000..ee1468f
--- /dev/null
+++ b/client/src/components/panels/contextual-layers/item.tsx
@@ -0,0 +1,23 @@
+import { Dataset, Layer } from "@/types/generated/strapi.schemas";
+
+interface ItemProps {
+ name: Dataset["name"];
+ layers: { id: number; name: Layer["name"] }[];
+}
+
+const Item = ({ name, layers }: ItemProps) => {
+ return (
+
+
{name}
+
+ {layers.map((layer) => (
+
+ {layer.name}
+
+ ))}
+
+
+ );
+};
+
+export default Item;
diff --git a/client/src/components/ui/button.tsx b/client/src/components/ui/button.tsx
index 99cc045..696bfa6 100644
--- a/client/src/components/ui/button.tsx
+++ b/client/src/components/ui/button.tsx
@@ -17,7 +17,7 @@ const buttonVariants = cva(
"bg-neutral-900 text-neutral-50 hover:bg-neutral-900/90 ring-offset-white focus-visible:ring-neutral-950",
},
size: {
- default: "h-10 px-4 py-2",
+ default: "h-8 w-auto xl:h-10 px-4 xl:py-2",
icon: "h-8 w-8 xl:h-10 xl:w-10",
auto: "",
},
diff --git a/client/src/components/ui/sheet.tsx b/client/src/components/ui/sheet.tsx
index 1bf5738..c5e2d45 100644
--- a/client/src/components/ui/sheet.tsx
+++ b/client/src/components/ui/sheet.tsx
@@ -30,20 +30,25 @@ const SheetOverlay = React.forwardRef<
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva(
- "fixed z-10 bg-rhino-blue-900 text-white p-5 transition ease-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-500 data-[state=open]:duration-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950",
+ "group fixed z-10 p-5 transition ease-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-500 data-[state=open]:duration-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950",
{
variants: {
side: {
top: "inset-x-0 top-0 border-t border-t-white/30 data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-b border-b-white/30 data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
- left: "inset-y-0 left-0 h-full w-3/4 border-l border-l-white/30 data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
+ left: "inset-y-0 left-0 h-full w-full border-l border-l-white/30 data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:w-[400px]",
right:
- "inset-y-0 right-0 h-full w-3/4 border-r border-r-white/30 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
+ "inset-y-0 right-0 h-full w-full border-r border-r-white/30 data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:w-[400px]",
+ },
+ variant: {
+ default: "bg-rhino-blue-900 text-white",
+ light: "bg-white text-rhino-blue-950",
},
},
defaultVariants: {
side: "right",
+ variant: "default",
},
},
);
@@ -55,9 +60,13 @@ interface SheetContentProps
const SheetContent = React.forwardRef<
React.ElementRef,
SheetContentProps
->(({ side = "right", className, children, ...props }, ref) => (
+>(({ side = "right", variant = "default", className, children, ...props }, ref) => (
-
+
{children}
diff --git a/client/src/components/ui/skeleton.tsx b/client/src/components/ui/skeleton.tsx
new file mode 100644
index 0000000..c9173e0
--- /dev/null
+++ b/client/src/components/ui/skeleton.tsx
@@ -0,0 +1,7 @@
+import { cn } from "@/lib/utils";
+
+function Skeleton({ className, ...props }: React.HTMLAttributes) {
+ return
;
+}
+
+export { Skeleton };
diff --git a/client/src/env.ts b/client/src/env.ts
index a05251c..8bbe47b 100644
--- a/client/src/env.ts
+++ b/client/src/env.ts
@@ -24,6 +24,8 @@ export const env = createEnv({
NEXT_PUBLIC_MAPBOX_TOKEN: z.string(),
/** Mapbox' style URL that contains the basemap */
NEXT_PUBLIC_MAPBOX_STYLE: z.string(),
+ /** Strapi's instance URL (without trailing slash) */
+ NEXT_PUBLIC_API_URL: z.string(),
},
/*
* Due to how Next.js bundles environment variables on Edge and Client,
@@ -35,5 +37,6 @@ export const env = createEnv({
NEXT_PUBLIC_MAPBOX_TOKEN: process.env.NEXT_PUBLIC_MAPBOX_TOKEN,
NEXT_PUBLIC_MAPBOX_STYLE: process.env.NEXT_PUBLIC_MAPBOX_STYLE,
NEXT_USE_RESTRICTIVE_ROBOTS_TXT: process.env.NEXT_USE_RESTRICTIVE_ROBOTS_TXT,
+ NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
},
});
diff --git a/client/src/hooks/use-datasets-by-sub-topic.ts b/client/src/hooks/use-datasets-by-sub-topic.ts
new file mode 100644
index 0000000..6a7a97c
--- /dev/null
+++ b/client/src/hooks/use-datasets-by-sub-topic.ts
@@ -0,0 +1,72 @@
+import { useGetDatasets } from "@/types/generated/dataset";
+import { DatasetLayersDataItem } from "@/types/generated/strapi.schemas";
+
+type DatasetsBySubTopic = {
+ subTopic: string;
+ datasets: { id: number; name: string; layers: DatasetLayersDataItem[] }[];
+};
+
+export default function useDatasetsBySubTopic(topicSlug: string, layersFields = ["name"]) {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore-error
+ const { data, isLoading } = useGetDatasets(
+ {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore-error
+ fields: ["name"],
+ populate: {
+ sub_topic: {
+ fields: ["name"],
+ },
+ layers: {
+ fields: layersFields,
+ },
+ },
+ filters: {
+ topic: {
+ slug: {
+ $eq: topicSlug,
+ },
+ },
+ },
+ sort: "sub_topic.name",
+ },
+ {
+ query: {
+ placeholderData: { data: [] },
+ select: (data) => {
+ const res: DatasetsBySubTopic[] = [];
+
+ if (!data?.data) {
+ return res;
+ }
+
+ let currentIndex = -1;
+ let currentSubTopic = null;
+
+ for (const item of data.data) {
+ const subTopic = item.attributes!.sub_topic!.data!.attributes!.name!;
+ const dataset = item.attributes!.name;
+ const layers = item.attributes!.layers!.data!;
+
+ if (currentSubTopic === null || currentSubTopic !== subTopic) {
+ currentSubTopic = subTopic;
+ currentIndex++;
+ res[currentIndex] = { subTopic: currentSubTopic, datasets: [] };
+ }
+
+ res[currentIndex].datasets.push({
+ id: item.id!,
+ name: dataset,
+ layers,
+ });
+ }
+
+ return res;
+ },
+ },
+ },
+ );
+
+ return { data, isLoading };
+}
diff --git a/client/src/orval.config.ts b/client/src/orval.config.ts
new file mode 100644
index 0000000..8f7e87d
--- /dev/null
+++ b/client/src/orval.config.ts
@@ -0,0 +1,28 @@
+import { defineConfig } from "orval";
+
+export default defineConfig({
+ cms: {
+ input: {
+ target: "../../cms/src/extensions/documentation/documentation/1.0.0/full_documentation.json",
+ filters: {
+ tags: ["Topic", "Sub-topic", "Dataset", "Layer"],
+ },
+ },
+ output: {
+ target: "./types/generated/strapi.ts",
+ mode: "tags",
+ client: "react-query",
+ clean: true,
+ override: {
+ mutator: {
+ path: "./services/api.ts",
+ name: "API",
+ },
+ query: {
+ useQuery: true,
+ signal: true,
+ },
+ },
+ },
+ },
+});
diff --git a/client/src/services/api.ts b/client/src/services/api.ts
new file mode 100644
index 0000000..59966ce
--- /dev/null
+++ b/client/src/services/api.ts
@@ -0,0 +1,25 @@
+import Axios, { AxiosError, AxiosRequestConfig } from "axios";
+
+export const AXIOS_INSTANCE = Axios.create({ baseURL: process.env.NEXT_PUBLIC_API_URL });
+
+export const API = (config: AxiosRequestConfig, options?: AxiosRequestConfig): Promise => {
+ // eslint-disable-next-line import/no-named-as-default-member
+ const source = Axios.CancelToken.source();
+ const promise = AXIOS_INSTANCE({
+ ...config,
+ ...options,
+ cancelToken: source.token,
+ }).then(({ data }) => data);
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ promise.cancel = () => {
+ source.cancel("Query was cancelled");
+ };
+ return promise;
+};
+
+export type BodyType = BodyData;
+
+export type ErrorType = AxiosError;
+
+export default API;
diff --git a/client/src/svgs/layers.svg b/client/src/svgs/layers.svg
new file mode 100644
index 0000000..5bb51c6
--- /dev/null
+++ b/client/src/svgs/layers.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/client/src/svgs/xmark.svg b/client/src/svgs/xmark.svg
new file mode 100644
index 0000000..537b4d8
--- /dev/null
+++ b/client/src/svgs/xmark.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/client/src/types/generated/dataset.ts b/client/src/types/generated/dataset.ts
new file mode 100644
index 0000000..3c5a806
--- /dev/null
+++ b/client/src/types/generated/dataset.ts
@@ -0,0 +1,353 @@
+/**
+ * Generated by orval v7.2.0 🍺
+ * Do not edit manually.
+ * DOCUMENTATION
+ * OpenAPI spec version: 1.0.0
+ */
+import {
+ useMutation,
+ useQuery
+} from '@tanstack/react-query'
+import type {
+ DefinedInitialDataOptions,
+ DefinedUseQueryResult,
+ MutationFunction,
+ QueryFunction,
+ QueryKey,
+ UndefinedInitialDataOptions,
+ UseMutationOptions,
+ UseMutationResult,
+ UseQueryOptions,
+ UseQueryResult
+} from '@tanstack/react-query'
+import type {
+ DatasetListResponse,
+ DatasetRequest,
+ DatasetResponse,
+ Error,
+ GetDatasetsParams
+} from './strapi.schemas'
+import { API } from '../../services/api';
+import type { ErrorType, BodyType } from '../../services/api';
+
+
+
+type SecondParameter any> = Parameters[1];
+
+
+export const getDatasets = (
+ params?: GetDatasetsParams,
+ options?: SecondParameter,signal?: AbortSignal
+) => {
+
+
+ return API(
+ {url: `/datasets`, method: 'GET',
+ params, signal
+ },
+ options);
+ }
+
+
+export const getGetDatasetsQueryKey = (params?: GetDatasetsParams,) => {
+ return [`/datasets`, ...(params ? [params]: [])] as const;
+ }
+
+
+export const getGetDatasetsQueryOptions = >, TError = ErrorType>(params?: GetDatasetsParams, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter}
+) => {
+
+const {query: queryOptions, request: requestOptions} = options ?? {};
+
+ const queryKey = queryOptions?.queryKey ?? getGetDatasetsQueryKey(params);
+
+
+
+ const queryFn: QueryFunction>> = ({ signal }) => getDatasets(params, requestOptions, signal);
+
+
+
+
+
+ return { queryKey, queryFn, ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: QueryKey }
+}
+
+export type GetDatasetsQueryResult = NonNullable>>
+export type GetDatasetsQueryError = ErrorType
+
+
+export function useGetDatasets>, TError = ErrorType>(
+ params: undefined | GetDatasetsParams, options: { query:Partial>, TError, TData>> & Pick<
+ DefinedInitialDataOptions<
+ Awaited>,
+ TError,
+ TData
+ > , 'initialData'
+ >, request?: SecondParameter}
+
+ ): DefinedUseQueryResult & { queryKey: QueryKey }
+export function useGetDatasets>, TError = ErrorType>(
+ params?: GetDatasetsParams, options?: { query?:Partial>, TError, TData>> & Pick<
+ UndefinedInitialDataOptions<
+ Awaited>,
+ TError,
+ TData
+ > , 'initialData'
+ >, request?: SecondParameter}
+
+ ): UseQueryResult & { queryKey: QueryKey }
+export function useGetDatasets>, TError = ErrorType>(
+ params?: GetDatasetsParams, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter}
+
+ ): UseQueryResult & { queryKey: QueryKey }
+
+export function useGetDatasets>, TError = ErrorType>(
+ params?: GetDatasetsParams, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter}
+
+ ): UseQueryResult & { queryKey: QueryKey } {
+
+ const queryOptions = getGetDatasetsQueryOptions(params,options)
+
+ const query = useQuery(queryOptions) as UseQueryResult & { queryKey: QueryKey };
+
+ query.queryKey = queryOptions.queryKey ;
+
+ return query;
+}
+
+
+
+export const postDatasets = (
+ datasetRequest: BodyType,
+ options?: SecondParameter,) => {
+
+
+ return API(
+ {url: `/datasets`, method: 'POST',
+ headers: {'Content-Type': 'application/json', },
+ data: datasetRequest
+ },
+ options);
+ }
+
+
+
+export const getPostDatasetsMutationOptions = ,
+ TContext = unknown>(options?: { mutation?:UseMutationOptions>, TError,{data: BodyType}, TContext>, request?: SecondParameter}
+): UseMutationOptions>, TError,{data: BodyType}, TContext> => {
+const {mutation: mutationOptions, request: requestOptions} = options ?? {};
+
+
+
+
+ const mutationFn: MutationFunction>, {data: BodyType}> = (props) => {
+ const {data} = props ?? {};
+
+ return postDatasets(data,requestOptions)
+ }
+
+
+
+
+ return { mutationFn, ...mutationOptions }}
+
+ export type PostDatasetsMutationResult = NonNullable>>
+ export type PostDatasetsMutationBody = BodyType
+ export type PostDatasetsMutationError = ErrorType
+
+ export const usePostDatasets = ,
+ TContext = unknown>(options?: { mutation?:UseMutationOptions>, TError,{data: BodyType}, TContext>, request?: SecondParameter}
+): UseMutationResult<
+ Awaited>,
+ TError,
+ {data: BodyType},
+ TContext
+ > => {
+
+ const mutationOptions = getPostDatasetsMutationOptions(options);
+
+ return useMutation(mutationOptions);
+ }
+ export const getDatasetsId = (
+ id: number,
+ options?: SecondParameter,signal?: AbortSignal
+) => {
+
+
+ return API(
+ {url: `/datasets/${id}`, method: 'GET', signal
+ },
+ options);
+ }
+
+
+export const getGetDatasetsIdQueryKey = (id: number,) => {
+ return [`/datasets/${id}`] as const;
+ }
+
+
+export const getGetDatasetsIdQueryOptions = >, TError = ErrorType>(id: number, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter}
+) => {
+
+const {query: queryOptions, request: requestOptions} = options ?? {};
+
+ const queryKey = queryOptions?.queryKey ?? getGetDatasetsIdQueryKey(id);
+
+
+
+ const queryFn: QueryFunction>> = ({ signal }) => getDatasetsId(id, requestOptions, signal);
+
+
+
+
+
+ return { queryKey, queryFn, enabled: !!(id), ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: QueryKey }
+}
+
+export type GetDatasetsIdQueryResult = NonNullable>>
+export type GetDatasetsIdQueryError = ErrorType
+
+
+export function useGetDatasetsId>, TError = ErrorType>(
+ id: number, options: { query:Partial>, TError, TData>> & Pick<
+ DefinedInitialDataOptions<
+ Awaited>,
+ TError,
+ TData
+ > , 'initialData'
+ >, request?: SecondParameter}
+
+ ): DefinedUseQueryResult & { queryKey: QueryKey }
+export function useGetDatasetsId>, TError = ErrorType>(
+ id: number, options?: { query?:Partial>, TError, TData>> & Pick<
+ UndefinedInitialDataOptions<
+ Awaited>,
+ TError,
+ TData
+ > , 'initialData'
+ >, request?: SecondParameter}
+
+ ): UseQueryResult & { queryKey: QueryKey }
+export function useGetDatasetsId>, TError = ErrorType>(
+ id: number, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter}
+
+ ): UseQueryResult & { queryKey: QueryKey }
+
+export function useGetDatasetsId>, TError = ErrorType>(
+ id: number, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter}
+
+ ): UseQueryResult & { queryKey: QueryKey } {
+
+ const queryOptions = getGetDatasetsIdQueryOptions(id,options)
+
+ const query = useQuery(queryOptions) as UseQueryResult & { queryKey: QueryKey };
+
+ query.queryKey = queryOptions.queryKey ;
+
+ return query;
+}
+
+
+
+export const putDatasetsId = (
+ id: number,
+ datasetRequest: BodyType,
+ options?: SecondParameter,) => {
+
+
+ return API(
+ {url: `/datasets/${id}`, method: 'PUT',
+ headers: {'Content-Type': 'application/json', },
+ data: datasetRequest
+ },
+ options);
+ }
+
+
+
+export const getPutDatasetsIdMutationOptions = ,
+ TContext = unknown>(options?: { mutation?:UseMutationOptions>, TError,{id: number;data: BodyType}, TContext>, request?: SecondParameter}
+): UseMutationOptions>, TError,{id: number;data: BodyType}, TContext> => {
+const {mutation: mutationOptions, request: requestOptions} = options ?? {};
+
+
+
+
+ const mutationFn: MutationFunction>, {id: number;data: BodyType}> = (props) => {
+ const {id,data} = props ?? {};
+
+ return putDatasetsId(id,data,requestOptions)
+ }
+
+
+
+
+ return { mutationFn, ...mutationOptions }}
+
+ export type PutDatasetsIdMutationResult = NonNullable>>
+ export type PutDatasetsIdMutationBody = BodyType
+ export type PutDatasetsIdMutationError = ErrorType
+
+ export const usePutDatasetsId = ,
+ TContext = unknown>(options?: { mutation?:UseMutationOptions>, TError,{id: number;data: BodyType}, TContext>, request?: SecondParameter}
+): UseMutationResult<
+ Awaited>,
+ TError,
+ {id: number;data: BodyType},
+ TContext
+ > => {
+
+ const mutationOptions = getPutDatasetsIdMutationOptions(options);
+
+ return useMutation(mutationOptions);
+ }
+ export const deleteDatasetsId = (
+ id: number,
+ options?: SecondParameter,) => {
+
+
+ return API(
+ {url: `/datasets/${id}`, method: 'DELETE'
+ },
+ options);
+ }
+
+
+
+export const getDeleteDatasetsIdMutationOptions = ,
+ TContext = unknown>(options?: { mutation?:UseMutationOptions>, TError,{id: number}, TContext>, request?: SecondParameter}
+): UseMutationOptions>, TError,{id: number}, TContext> => {
+const {mutation: mutationOptions, request: requestOptions} = options ?? {};
+
+
+
+
+ const mutationFn: MutationFunction>, {id: number}> = (props) => {
+ const {id} = props ?? {};
+
+ return deleteDatasetsId(id,requestOptions)
+ }
+
+
+
+
+ return { mutationFn, ...mutationOptions }}
+
+ export type DeleteDatasetsIdMutationResult = NonNullable>>
+
+ export type DeleteDatasetsIdMutationError = ErrorType
+
+ export const useDeleteDatasetsId = ,
+ TContext = unknown>(options?: { mutation?:UseMutationOptions>, TError,{id: number}, TContext>, request?: SecondParameter}
+): UseMutationResult<
+ Awaited>,
+ TError,
+ {id: number},
+ TContext
+ > => {
+
+ const mutationOptions = getDeleteDatasetsIdMutationOptions(options);
+
+ return useMutation(mutationOptions);
+ }
+
\ No newline at end of file
diff --git a/client/src/types/generated/layer.ts b/client/src/types/generated/layer.ts
new file mode 100644
index 0000000..d89ee35
--- /dev/null
+++ b/client/src/types/generated/layer.ts
@@ -0,0 +1,353 @@
+/**
+ * Generated by orval v7.2.0 🍺
+ * Do not edit manually.
+ * DOCUMENTATION
+ * OpenAPI spec version: 1.0.0
+ */
+import {
+ useMutation,
+ useQuery
+} from '@tanstack/react-query'
+import type {
+ DefinedInitialDataOptions,
+ DefinedUseQueryResult,
+ MutationFunction,
+ QueryFunction,
+ QueryKey,
+ UndefinedInitialDataOptions,
+ UseMutationOptions,
+ UseMutationResult,
+ UseQueryOptions,
+ UseQueryResult
+} from '@tanstack/react-query'
+import type {
+ Error,
+ GetLayersParams,
+ LayerListResponse,
+ LayerRequest,
+ LayerResponse
+} from './strapi.schemas'
+import { API } from '../../services/api';
+import type { ErrorType, BodyType } from '../../services/api';
+
+
+
+type SecondParameter any> = Parameters[1];
+
+
+export const getLayers = (
+ params?: GetLayersParams,
+ options?: SecondParameter,signal?: AbortSignal
+) => {
+
+
+ return API(
+ {url: `/layers`, method: 'GET',
+ params, signal
+ },
+ options);
+ }
+
+
+export const getGetLayersQueryKey = (params?: GetLayersParams,) => {
+ return [`/layers`, ...(params ? [params]: [])] as const;
+ }
+
+
+export const getGetLayersQueryOptions = >, TError = ErrorType>(params?: GetLayersParams, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter}
+) => {
+
+const {query: queryOptions, request: requestOptions} = options ?? {};
+
+ const queryKey = queryOptions?.queryKey ?? getGetLayersQueryKey(params);
+
+
+
+ const queryFn: QueryFunction>> = ({ signal }) => getLayers(params, requestOptions, signal);
+
+
+
+
+
+ return { queryKey, queryFn, ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: QueryKey }
+}
+
+export type GetLayersQueryResult = NonNullable>>
+export type GetLayersQueryError = ErrorType
+
+
+export function useGetLayers>, TError = ErrorType>(
+ params: undefined | GetLayersParams, options: { query:Partial>, TError, TData>> & Pick<
+ DefinedInitialDataOptions<
+ Awaited>,
+ TError,
+ TData
+ > , 'initialData'
+ >, request?: SecondParameter}
+
+ ): DefinedUseQueryResult & { queryKey: QueryKey }
+export function useGetLayers>, TError = ErrorType>(
+ params?: GetLayersParams, options?: { query?:Partial>, TError, TData>> & Pick<
+ UndefinedInitialDataOptions<
+ Awaited>,
+ TError,
+ TData
+ > , 'initialData'
+ >, request?: SecondParameter}
+
+ ): UseQueryResult & { queryKey: QueryKey }
+export function useGetLayers>, TError = ErrorType>(
+ params?: GetLayersParams, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter}
+
+ ): UseQueryResult & { queryKey: QueryKey }
+
+export function useGetLayers>, TError = ErrorType>(
+ params?: GetLayersParams, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter}
+
+ ): UseQueryResult & { queryKey: QueryKey } {
+
+ const queryOptions = getGetLayersQueryOptions(params,options)
+
+ const query = useQuery(queryOptions) as UseQueryResult & { queryKey: QueryKey };
+
+ query.queryKey = queryOptions.queryKey ;
+
+ return query;
+}
+
+
+
+export const postLayers = (
+ layerRequest: BodyType,
+ options?: SecondParameter,) => {
+
+
+ return API(
+ {url: `/layers`, method: 'POST',
+ headers: {'Content-Type': 'application/json', },
+ data: layerRequest
+ },
+ options);
+ }
+
+
+
+export const getPostLayersMutationOptions = ,
+ TContext = unknown>(options?: { mutation?:UseMutationOptions>, TError,{data: BodyType}, TContext>, request?: SecondParameter}
+): UseMutationOptions>, TError,{data: BodyType}, TContext> => {
+const {mutation: mutationOptions, request: requestOptions} = options ?? {};
+
+
+
+
+ const mutationFn: MutationFunction>, {data: BodyType}> = (props) => {
+ const {data} = props ?? {};
+
+ return postLayers(data,requestOptions)
+ }
+
+
+
+
+ return { mutationFn, ...mutationOptions }}
+
+ export type PostLayersMutationResult = NonNullable>>
+ export type PostLayersMutationBody = BodyType
+ export type PostLayersMutationError = ErrorType
+
+ export const usePostLayers = ,
+ TContext = unknown>(options?: { mutation?:UseMutationOptions>, TError,{data: BodyType}, TContext>, request?: SecondParameter}
+): UseMutationResult<
+ Awaited>,
+ TError,
+ {data: BodyType},
+ TContext
+ > => {
+
+ const mutationOptions = getPostLayersMutationOptions(options);
+
+ return useMutation(mutationOptions);
+ }
+ export const getLayersId = (
+ id: number,
+ options?: SecondParameter,signal?: AbortSignal
+) => {
+
+
+ return API(
+ {url: `/layers/${id}`, method: 'GET', signal
+ },
+ options);
+ }
+
+
+export const getGetLayersIdQueryKey = (id: number,) => {
+ return [`/layers/${id}`] as const;
+ }
+
+
+export const getGetLayersIdQueryOptions = >, TError = ErrorType>(id: number, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter}
+) => {
+
+const {query: queryOptions, request: requestOptions} = options ?? {};
+
+ const queryKey = queryOptions?.queryKey ?? getGetLayersIdQueryKey(id);
+
+
+
+ const queryFn: QueryFunction>> = ({ signal }) => getLayersId(id, requestOptions, signal);
+
+
+
+
+
+ return { queryKey, queryFn, enabled: !!(id), ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: QueryKey }
+}
+
+export type GetLayersIdQueryResult = NonNullable>>
+export type GetLayersIdQueryError = ErrorType
+
+
+export function useGetLayersId>, TError = ErrorType>(
+ id: number, options: { query:Partial>, TError, TData>> & Pick<
+ DefinedInitialDataOptions<
+ Awaited>,
+ TError,
+ TData
+ > , 'initialData'
+ >, request?: SecondParameter}
+
+ ): DefinedUseQueryResult & { queryKey: QueryKey }
+export function useGetLayersId>, TError = ErrorType>(
+ id: number, options?: { query?:Partial>, TError, TData>> & Pick<
+ UndefinedInitialDataOptions<
+ Awaited>,
+ TError,
+ TData
+ > , 'initialData'
+ >, request?: SecondParameter}
+
+ ): UseQueryResult & { queryKey: QueryKey }
+export function useGetLayersId>, TError = ErrorType>(
+ id: number, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter}
+
+ ): UseQueryResult & { queryKey: QueryKey }
+
+export function useGetLayersId>, TError = ErrorType>(
+ id: number, options?: { query?:Partial>, TError, TData>>, request?: SecondParameter}
+
+ ): UseQueryResult & { queryKey: QueryKey } {
+
+ const queryOptions = getGetLayersIdQueryOptions(id,options)
+
+ const query = useQuery(queryOptions) as UseQueryResult & { queryKey: QueryKey };
+
+ query.queryKey = queryOptions.queryKey ;
+
+ return query;
+}
+
+
+
+export const putLayersId = (
+ id: number,
+ layerRequest: BodyType,
+ options?: SecondParameter,) => {
+
+
+ return API(
+ {url: `/layers/${id}`, method: 'PUT',
+ headers: {'Content-Type': 'application/json', },
+ data: layerRequest
+ },
+ options);
+ }
+
+
+
+export const getPutLayersIdMutationOptions = ,
+ TContext = unknown>(options?: { mutation?:UseMutationOptions>, TError,{id: number;data: BodyType}, TContext>, request?: SecondParameter}
+): UseMutationOptions>, TError,{id: number;data: BodyType}, TContext> => {
+const {mutation: mutationOptions, request: requestOptions} = options ?? {};
+
+
+
+
+ const mutationFn: MutationFunction>, {id: number;data: BodyType}> = (props) => {
+ const {id,data} = props ?? {};
+
+ return putLayersId(id,data,requestOptions)
+ }
+
+
+
+
+ return { mutationFn, ...mutationOptions }}
+
+ export type PutLayersIdMutationResult = NonNullable>>
+ export type PutLayersIdMutationBody = BodyType
+ export type PutLayersIdMutationError = ErrorType
+
+ export const usePutLayersId = ,
+ TContext = unknown>(options?: { mutation?:UseMutationOptions>, TError,{id: number;data: BodyType}, TContext>, request?: SecondParameter}
+): UseMutationResult<
+ Awaited>,
+ TError,
+ {id: number;data: BodyType},
+ TContext
+ > => {
+
+ const mutationOptions = getPutLayersIdMutationOptions(options);
+
+ return useMutation(mutationOptions);
+ }
+ export const deleteLayersId = (
+ id: number,
+ options?: SecondParameter,) => {
+
+
+ return API(
+ {url: `/layers/${id}`, method: 'DELETE'
+ },
+ options);
+ }
+
+
+
+export const getDeleteLayersIdMutationOptions = ,
+ TContext = unknown>(options?: { mutation?:UseMutationOptions>, TError,{id: number}, TContext>, request?: SecondParameter}
+): UseMutationOptions>, TError,{id: number}, TContext> => {
+const {mutation: mutationOptions, request: requestOptions} = options ?? {};
+
+
+
+
+ const mutationFn: MutationFunction>, {id: number}> = (props) => {
+ const {id} = props ?? {};
+
+ return deleteLayersId(id,requestOptions)
+ }
+
+
+
+
+ return { mutationFn, ...mutationOptions }}
+
+ export type DeleteLayersIdMutationResult = NonNullable>>
+
+ export type DeleteLayersIdMutationError = ErrorType
+
+ export const useDeleteLayersId = ,
+ TContext = unknown>(options?: { mutation?:UseMutationOptions>, TError,{id: number}, TContext>, request?: SecondParameter}
+): UseMutationResult<
+ Awaited