Skip to content
Snippets Groups Projects
Commit 84f63444 authored by Moritz Stückler's avatar Moritz Stückler :cowboy:
Browse files

Merge branch 'feat/filterTags' into 'develop'

Feat/filter tags

See merge request software/fabcity-map/fabcity-map-frontend!16
parents f570d5ce 95d9d2de
No related branches found
No related tags found
No related merge requests found
Showing
with 4067 additions and 6485 deletions
overwrite: true
schema: 'http://localhost:8000/graphql'
documents: 'src/graphql/**/*.ts'
generates:
src/generated/graphql.ts:
plugins:
- 'typescript'
- 'typescript-operations'
- 'typescript-graphql-request'
config:
useTypeImports: true
This diff is collapsed.
......@@ -5,7 +5,8 @@
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"",
"prettier:lint": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"",
"eslint": "eslint \"src/**/*.{js,jsx,ts,tsx}\"",
"copy:htaccess": "cp public/.htaccess build/"
"copy:htaccess": "cp public/.htaccess build/",
"generate": "graphql-codegen --config codegen.yml"
},
"dependencies": {
"@tailwindcss/forms": "^0.3.2",
......@@ -23,6 +24,10 @@
"zustand": "^3.5.1"
},
"devDependencies": {
"@graphql-codegen/cli": "1.21.4",
"@graphql-codegen/typescript": "1.22.0",
"@graphql-codegen/typescript-graphql-request": "^3.2.0",
"@graphql-codegen/typescript-operations": "1.17.16",
"@snowpack/plugin-dotenv": "^2.1.0",
"@snowpack/plugin-postcss": "^1.3.0",
"@snowpack/plugin-react-refresh": "^2.4.0",
......
......@@ -12,6 +12,7 @@ import TagInput from './TagInput';
import { removeDuplicateObjects } from '../../util/array';
import { createPoi, createTags } from '../../graphql/mutations';
import Spinner from '../Spinner';
import type { CreatePoiMutationMutationVariables, Mutation } from '../../generated/graphql';
const AddPoiForm: React.FC = () => {
const [formData, setFormData] = useState<PointOfInterestFormData>({
......@@ -56,32 +57,36 @@ const AddPoiForm: React.FC = () => {
setIsLoading(true);
try {
const newTags: Tag[] = [];
const newTagIdsResponse: number[] = [];
let newTagIdsResponse: string[] = [];
const oldTags: Tag[] = [];
selectedTags.forEach((tag) => (tag.id === 'draft' ? newTags.push(tag) : oldTags.push(tag)));
if (newTags.length) {
const tagsRes = await fetcher(createTags, {
const tagsRes: Mutation = await fetcher(createTags, {
tags: newTags.map(({ displayName, color }) => ({
displayName,
color,
})),
});
if (tagsRes.createTags.length > 0) {
tagsRes.createTags.map((newTagResponse: { id: string }) => {
newTagIdsResponse.push(Number(newTagResponse.id));
});
if (tagsRes.createTags && tagsRes.createTags.length > 0) {
newTagIdsResponse = tagsRes.createTags
.filter((newTagResponse) => newTagResponse?.id)
.map((newTagResponse) => {
return (newTagResponse as Tag).id;
});
}
}
const finalData = { ...formData, tagIds: [...newTagIdsResponse, ...oldTags.map((oldTag) => Number(oldTag.id))] };
const finalData: CreatePoiMutationMutationVariables = {
...formData,
tagIds: [...newTagIdsResponse, ...oldTags.map((oldTag) => oldTag.id)],
};
const res = await fetcher(createPoi, finalData);
const res: Mutation = await fetcher(createPoi, finalData);
if (res.createPoi) {
setNotification({
title: 'Ort hinzugefügt',
text:
'Ort wurde erfolgreich hinzugefügt. Bitte überprüfe deine E-Mails und klicke dort auf den Link um deine Mail-Adresse zu verifizieren.',
text: 'Ort wurde erfolgreich hinzugefügt. Bitte überprüfe deine E-Mails und klicke dort auf den Link um deine Mail-Adresse zu verifizieren.',
type: 'success',
});
history.push('/');
......@@ -127,10 +132,6 @@ const AddPoiForm: React.FC = () => {
if (draftPoi) setFormData({ ...formData, lat: draftPoi[0], lng: draftPoi[1] });
}, [draftPoi]);
useEffect(() => {
console.log('Form Data', formData);
}, [formData]);
return (
<form className="flex-1 flex flex-col justify-between" onSubmit={handleSubmit} ref={formRef}>
<div className="flex flex-col">
......
......@@ -11,8 +11,7 @@ interface Props {
required?: boolean;
}
const TagInput: React.FC = ({ label, tags, options, onTagsChange, required }: Props) => {
// const [allTags, setAllTags] = useState<TagType[]>([]);
const TagInput: React.FC<Props> = ({ label, tags, options, onTagsChange, required }: Props) => {
// All options
const [tagOptions, setTagOptions] = useState<TagType[]>([]);
// The text input
......@@ -100,7 +99,7 @@ const TagInput: React.FC = ({ label, tags, options, onTagsChange, required }: Pr
}}
type={'text'}
value={draftEntry}
className={`w-16 block p-0 flex-1 rounded-lg border-0 focus:outline-none focus:ring-0 ${
className={`form-input w-16 block p-0 flex-1 rounded-lg border-0 focus:outline-none focus:ring-0 ${
tags.length ? '' : 'px-3'
}`}
/>
......
......@@ -20,7 +20,7 @@ const TextAreaInput: React.FC<Props> = ({ name, label, value, onChange, ...textA
<textarea
name={name}
value={value}
className="form-input"
className="form-textarea form-input-custom"
rows={3}
onChange={onChange}
{...textAreaProps}
......
......@@ -18,7 +18,14 @@ const TextInput: React.FC<Props> = ({ name, label, value, onChange, type = 'text
{inputProps?.required && `*`}
</span>
)}
<input type={type} name={name} value={value} className="form-input" onChange={onChange} {...inputProps} />
<input
type={type}
name={name}
value={value}
className="form-input form-input-custom"
onChange={onChange}
{...inputProps}
/>
</label>
);
};
......
......@@ -2,8 +2,10 @@ import type { LatLngTuple } from 'leaflet';
import { divIcon, DivIconOptions } from 'leaflet';
import React, { useMemo } from 'react';
import { MapContainer, Marker, TileLayer } from 'react-leaflet';
import { usePoiData, useStore } from '../../hooks';
import { useStore } from '../../hooks';
import MapViewController from './MapViewController';
import { useFilteredPoiData } from '../../hooks/useFilteredPoiData';
import MapLayerControl from './MapLayerControl';
interface Props {
createMode?: boolean;
......@@ -25,7 +27,7 @@ export const Map: React.FC<Props> = ({ createMode }) => {
() => divIcon({ ...iconProps, iconSize: [30, 40], iconAnchor: [15, 40], className: 'marker-green' }),
[iconProps],
);
const { data } = usePoiData();
const { data } = useFilteredPoiData();
const draftPoi = useStore((state) => state.draftPoi);
const hoveredPoi = useStore((state) => state.hoveredPoi);
const setHoveredPoi = useStore((state) => state.setHoveredPoi);
......@@ -34,45 +36,54 @@ export const Map: React.FC<Props> = ({ createMode }) => {
const selectedLatlng: LatLngTuple | undefined = selectedPoi ? [selectedPoi?.lat, selectedPoi?.lng] : undefined;
return (
<MapContainer id={'mapid'} className={'h-full w-full z-0'} center={DEFAULT_CENTER} zoom={13} scrollWheelZoom={true}>
<TileLayer
attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}"
id="mapbox/streets-v11"
tileSize={512}
accessToken={import.meta.env.SNOWPACK_PUBLIC_MAPBOX_TOKEN}
zoomOffset={-1}
maxZoom={18}
/>
<MapViewController center={selectedLatlng ?? DEFAULT_CENTER} zoom={13} createPoiMode={createMode} />
{/* Single marker when creating a new POI */}
{!!(createMode && draftPoi) && <Marker icon={greenLargeIcon} position={draftPoi} />}
{/* Single marker when POI is selected */}
{!!(selectedPoi && selectedLatlng && !createMode) && <Marker icon={largeIcon} position={selectedLatlng} />}
{/* Multiple markers, when no POI is selected */}
{!selectedPoi &&
!createMode &&
data?.map((poi) => {
const poiLatLng: LatLngTuple = [poi.lat, poi.lng];
return (
<Marker
icon={hoveredPoi?.id === poi.id ? largeIcon : icon}
opacity={hoveredPoi?.id === poi.id ? 1 : 0.7}
key={poi.id}
position={poiLatLng}
eventHandlers={{
click: () => setSelectedPoi(poi),
mouseover: () => {
setHoveredPoi(poi);
},
mouseout: () => {
setHoveredPoi(null);
},
}}
/>
);
})}
</MapContainer>
<div className="relative h-full w-full z-0">
<MapLayerControl />
<MapContainer
id={'mapid'}
className={'h-full w-full z-0'}
center={DEFAULT_CENTER}
zoom={13}
scrollWheelZoom={true}
>
<TileLayer
attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}"
id="mapbox/streets-v11"
tileSize={512}
accessToken={import.meta.env.SNOWPACK_PUBLIC_MAPBOX_TOKEN}
zoomOffset={-1}
maxZoom={18}
/>
<MapViewController center={selectedLatlng ?? DEFAULT_CENTER} zoom={13} createPoiMode={createMode} />
{/* Single marker when creating a new POI */}
{!!(createMode && draftPoi) && <Marker icon={greenLargeIcon} position={draftPoi} />}
{/* Single marker when POI is selected */}
{!!(selectedPoi && selectedLatlng && !createMode) && <Marker icon={largeIcon} position={selectedLatlng} />}
{/* Multiple markers, when no POI is selected */}
{!selectedPoi &&
!createMode &&
data?.map((poi) => {
const poiLatLng: LatLngTuple = [poi.lat, poi.lng];
return (
<Marker
icon={hoveredPoi?.id === poi.id ? largeIcon : icon}
opacity={hoveredPoi?.id === poi.id ? 1 : 0.7}
key={poi.id}
position={poiLatLng}
eventHandlers={{
click: () => setSelectedPoi(poi),
mouseover: () => {
setHoveredPoi(poi);
},
mouseout: () => {
setHoveredPoi(null);
},
}}
/>
);
})}
</MapContainer>
</div>
);
};
......
import React from 'react';
import { useFilteredPoiData } from '../../hooks/useFilteredPoiData';
import { usePoiData } from '../../hooks';
const MapLayerControl: React.FC = () => {
const { data } = usePoiData();
const { filterCategories, setFilterCategories } = useFilteredPoiData();
const layers = Array.from(new Set(data?.map((poi) => poi.category)));
const onChangeCheckbox = (state: boolean, layer: string) => {
if (state) {
setFilterCategories([...filterCategories, layer]);
} else {
setFilterCategories(filterCategories.filter((cat) => cat !== layer));
}
};
return (
<div className="border-2 border-black border-opacity-20 rounded-lg absolute right-0 bottom-0 mb-4 mr-4 bg-white p-4 z-10">
{layers.map((layer) => {
return (
<div key={layer}>
<label className="inline-flex items-center">
<input
type="checkbox"
className="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300 focus:ring focus:ring-offset-0 focus:ring-indigo-200 focus:ring-opacity-50"
checked={!!filterCategories?.find((cat) => cat === layer)}
onChange={(e) => onChangeCheckbox(e.target.checked, layer)}
/>
<span className="ml-2">{layer}</span>
</label>
</div>
);
})}
</div>
);
};
export default MapLayerControl;
import React from 'react';
import type { Theme } from 'react-select';
import Select, { NamedProps, StylesConfig } from 'react-select';
const MultiSelect = <OptionType,>(props: NamedProps<OptionType, true>): JSX.Element => {
const customStyles: StylesConfig<OptionType, true> = {
control: (provided) => ({ ...provided, border: '0', borderRadius: '0.5em' }),
multiValue: (provided) => ({ ...provided, borderRadius: '999px', padding: '0 3px' }),
multiValueRemove: (provided) => ({
...provided,
color: 'hsl(0, 0%, 50%)',
'&:hover': { backgroundColor: 'initial', color: 'black' },
}),
};
return (
<Select
theme={(theme): Theme => ({
...theme,
// @ts-expect-error: ThemeConfig type from definitely-typed is not complete
borderRadius: '0.5em',
colors: {
...theme.colors,
primary25: '#C7D2FE',
},
})}
styles={customStyles}
isMulti={true}
name="pois"
className="hover:border-opacity-40 rounded-lg w-full border-2 border-black border-opacity-20 focus-within:border-indigo-300 focus-within:ring focus-within:ring-indigo-200 focus-within:ring-opacity-50 mt-1"
{...props}
/>
);
};
export default MultiSelect;
import React from 'react';
import { usePoiData, useStore } from '../../hooks';
import ListElement from './ListElement';
import SidebarContainer from './SidebarContainer';
import Select from 'react-select';
const SidebarListView: React.FC = () => {
const { data } = usePoiData();
const hoveredPoi = useStore((state) => state.hoveredPoi);
const setHoveredPoi = useStore((state) => state.setHoveredPoi);
const setSelectedPoi = useStore((state) => state.setSelectedPoi);
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
return (
<SidebarContainer>
<div className="p-4">
<Select
defaultValue={[options[2], options[3]]}
isMulti
name="pois"
options={options}
className="basic-multi-select"
classNamePrefix="select"
/>
</div>
<h1 className="text-xl font-medium title-font m-4 text-gray-900 mb-2">{data?.length} Orte:</h1>
{data?.map((poi) => (
<ListElement
key={poi.id}
onMouseEnter={() => setHoveredPoi(poi)}
onMouseLeave={() => setHoveredPoi(null)}
onClick={() => setSelectedPoi(poi)}
value={poi}
hovered={hoveredPoi?.id === poi.id}
/>
))}
</SidebarContainer>
);
};
export default SidebarListView;
import React from 'react';
import { usePoiData, useStore } from '../../hooks';
import ListElement from './ListElement';
import SidebarContainer from './SidebarContainer';
import { removeDuplicateObjects } from '../../util/array';
import { useFilteredPoiData } from '../../hooks/useFilteredPoiData';
import type { Tag } from '../../types/PointOfInterest';
import MultiSelect from '../MultiSelect';
const SidebarListView: React.FC = () => {
const tagsToSelectOptions = (tags?: Tag[]) => tags?.map((tag) => ({ label: tag.displayName, value: tag }));
const { data } = usePoiData();
const { data: filteredData, filterTags, setFilterTags } = useFilteredPoiData();
const hoveredPoi = useStore((state) => state.hoveredPoi);
const setHoveredPoi = useStore((state) => state.setHoveredPoi);
const setSelectedPoi = useStore((state) => state.setSelectedPoi);
const tags =
data &&
removeDuplicateObjects(
data?.flatMap((poi) => poi.tags),
'id',
);
const options = tagsToSelectOptions(tags);
return (
<SidebarContainer>
<div className="p-4">
<MultiSelect
options={options}
value={filterTags && tagsToSelectOptions(filterTags)}
onChange={(selectedOptions) => setFilterTags(selectedOptions.map((opt) => opt.value))}
/>
</div>
<h1 className="text-xl font-medium title-font m-4 text-gray-900 mb-2">{filteredData?.length} Orte:</h1>
{filteredData?.map((poi) => (
<ListElement
key={poi.id}
onMouseEnter={() => setHoveredPoi(poi)}
onMouseLeave={() => setHoveredPoi(null)}
onClick={() => setSelectedPoi(poi)}
value={poi}
hovered={hoveredPoi?.id === poi.id}
/>
))}
</SidebarContainer>
);
};
export default SidebarListView;
import type { GraphQLClient } from 'graphql-request';
import type * as Dom from 'graphql-request/dist/types.dom';
import gql from 'graphql-tag';
export type Maybe<T> = T | null;
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
/** All built-in and custom scalars, mapped to their actual values */
export type Scalars = {
ID: string;
String: string;
Boolean: boolean;
Int: number;
Float: number;
/** A date string with format `Y-m-d`, e.g. `2011-05-23`. */
Date: any;
/** A datetime string with format `Y-m-d H:i:s`, e.g. `2018-05-23 13:43:32`. */
DateTime: any;
/** Can be used as an argument to upload files using https://github.com/jaydenseric/graphql-multipart-request-spec */
Upload: any;
};
export type Mutation = {
__typename?: 'Mutation';
createPoi?: Maybe<Scalars['Boolean']>;
createTags?: Maybe<Array<Maybe<Tag>>>;
};
export type MutationCreatePoiArgs = {
poi: PoiInput;
};
export type MutationCreateTagsArgs = {
tags: Array<TagInput>;
};
export type Poi = {
__typename?: 'POI';
id: Scalars['ID'];
lat: Scalars['Float'];
lng: Scalars['Float'];
name: Scalars['String'];
published: Scalars['Boolean'];
website?: Maybe<Scalars['String']>;
description?: Maybe<Scalars['String']>;
address: Scalars['String'];
image: Scalars['String'];
category: Scalars['String'];
tags?: Maybe<Array<Maybe<Tag>>>;
created_at?: Maybe<Scalars['DateTime']>;
updated_at?: Maybe<Scalars['DateTime']>;
};
export type PoiInput = {
name: Scalars['String'];
email: Scalars['String'];
lat: Scalars['Float'];
lng: Scalars['Float'];
website?: Maybe<Scalars['String']>;
description?: Maybe<Scalars['String']>;
address: Scalars['String'];
category: Scalars['String'];
image: Scalars['Upload'];
tagIds?: Maybe<Array<Maybe<Scalars['ID']>>>;
};
/** Pagination information about the corresponding list of items. */
export type PageInfo = {
__typename?: 'PageInfo';
/** When paginating forwards, are there more items? */
hasNextPage: Scalars['Boolean'];
/** When paginating backwards, are there more items? */
hasPreviousPage: Scalars['Boolean'];
/** When paginating backwards, the cursor to continue. */
startCursor?: Maybe<Scalars['String']>;
/** When paginating forwards, the cursor to continue. */
endCursor?: Maybe<Scalars['String']>;
/** Total number of node in connection. */
total?: Maybe<Scalars['Int']>;
/** Count of nodes in current request. */
count?: Maybe<Scalars['Int']>;
/** Current page of request. */
currentPage?: Maybe<Scalars['Int']>;
/** Last page in connection. */
lastPage?: Maybe<Scalars['Int']>;
};
/** Pagination information about the corresponding list of items. */
export type PaginatorInfo = {
__typename?: 'PaginatorInfo';
/** Total count of available items in the page. */
count: Scalars['Int'];
/** Current pagination page. */
currentPage: Scalars['Int'];
/** Index of first item in the current page. */
firstItem?: Maybe<Scalars['Int']>;
/** If collection has more pages. */
hasMorePages: Scalars['Boolean'];
/** Index of last item in the current page. */
lastItem?: Maybe<Scalars['Int']>;
/** Last page number of the collection. */
lastPage: Scalars['Int'];
/** Number of items per page in the collection. */
perPage: Scalars['Int'];
/** Total items available in the collection. */
total: Scalars['Int'];
};
export type Query = {
__typename?: 'Query';
pois?: Maybe<Array<Maybe<Poi>>>;
poi?: Maybe<Poi>;
};
export type QueryPoiArgs = {
id?: Maybe<Scalars['ID']>;
};
export type Tag = {
__typename?: 'Tag';
id: Scalars['ID'];
displayName: Scalars['String'];
color: Scalars['String'];
created_at?: Maybe<Scalars['DateTime']>;
updated_at?: Maybe<Scalars['DateTime']>;
};
export type TagInput = {
displayName: Scalars['String'];
color: Scalars['String'];
};
export type User = {
__typename?: 'User';
id: Scalars['ID'];
name: Scalars['String'];
email: Scalars['String'];
created_at: Scalars['DateTime'];
updated_at: Scalars['DateTime'];
};
export type CreatePoiMutationMutationVariables = Exact<{
name: Scalars['String'];
email: Scalars['String'];
lat: Scalars['Float'];
lng: Scalars['Float'];
website?: Maybe<Scalars['String']>;
description?: Maybe<Scalars['String']>;
address: Scalars['String'];
category: Scalars['String'];
image: Scalars['Upload'];
tagIds?: Maybe<Array<Maybe<Scalars['ID']>> | Maybe<Scalars['ID']>>;
}>;
export type CreatePoiMutationMutation = (
{ __typename?: 'Mutation' }
& Pick<Mutation, 'createPoi'>
);
export type CreateTagsMutationMutationVariables = Exact<{
tags: Array<TagInput> | TagInput;
}>;
export type CreateTagsMutationMutation = (
{ __typename?: 'Mutation' }
& { createTags?: Maybe<Array<Maybe<(
{ __typename?: 'Tag' }
& Pick<Tag, 'id'>
)>>> }
);
export const CreatePoiMutationDocument = gql`
mutation createPoiMutation($name: String!, $email: String!, $lat: Float!, $lng: Float!, $website: String, $description: String, $address: String!, $category: String!, $image: Upload!, $tagIds: [ID]) {
createPoi(
poi: {name: $name, email: $email, lat: $lat, lng: $lng, website: $website, description: $description, address: $address, category: $category, image: $image, tagIds: $tagIds}
)
}
`;
export const CreateTagsMutationDocument = gql`
mutation createTagsMutation($tags: [TagInput!]!) {
createTags(tags: $tags) {
id
}
}
`;
export type SdkFunctionWrapper = <T>(action: (requestHeaders?:Record<string, string>) => Promise<T>, operationName: string) => Promise<T>;
const defaultWrapper: SdkFunctionWrapper = (action, _operationName) => action();
export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = defaultWrapper) {
return {
createPoiMutation(variables: CreatePoiMutationMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<CreatePoiMutationMutation> {
return withWrapper((wrappedRequestHeaders) => client.request<CreatePoiMutationMutation>(CreatePoiMutationDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'createPoiMutation');
},
createTagsMutation(variables: CreateTagsMutationMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<CreateTagsMutationMutation> {
return withWrapper((wrappedRequestHeaders) => client.request<CreateTagsMutationMutation>(CreateTagsMutationDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'createTagsMutation');
}
};
}
export type Sdk = ReturnType<typeof getSdk>;
\ No newline at end of file
import type { poiData } from './usePoiData';
import { usePoiData } from './usePoiData';
import { useStore } from './useStore';
import type { Tag } from '../types/PointOfInterest';
export interface poiFilteredData extends poiData {
filterTags: Tag[] | null;
setFilterTags: (tags: Tag[]) => void;
filterCategories: string[];
setFilterCategories: (categories: string[]) => void;
}
export const useFilteredPoiData = (): poiFilteredData => {
const { data } = usePoiData();
const filterTags = useStore((store) => store.filterTags);
const setFilterTags = useStore((state) => state.setFilterTags);
const filterCategories = useStore((store) => store.filterCategories);
const setFilterCategories = useStore((state) => state.setFilterCategories);
let filteredData = data;
if (filterTags?.length) {
filteredData = filteredData?.filter(
(poi) => !filterTags.filter((fTag) => !poi.tags.find((tag) => tag.id === fTag.id)).length,
);
}
if (filterCategories?.length) {
filteredData = filteredData?.filter((poi) => !!filterCategories.find((category) => poi.category === category));
}
return {
data: filteredData,
filterTags,
setFilterTags,
filterCategories,
setFilterCategories,
};
};
import type { PointOfInterest, PointsOfInterestDTO } from 'src/types/PointOfInterest';
import useSWR from 'swr';
interface poiData {
export interface poiData {
data: PointOfInterest[] | undefined;
}
......
import type { Error } from 'src/types/Error';
import type { PointOfInterest } from 'src/types/PointOfInterest';
import type { PointOfInterest, Tag } from 'src/types/PointOfInterest';
import type { Notification } from 'src/types/Notification';
import type { LatLngTuple } from 'leaflet';
import create, { State } from 'zustand';
......@@ -16,6 +16,10 @@ interface Store extends State {
setDraftPoi: (latLng: LatLngTuple | null) => void;
notification: Notification | null;
setNotification: (notification: Notification | null) => void;
filterTags: Tag[];
setFilterTags: (tags: Tag[]) => void;
filterCategories: string[];
setFilterCategories: (categories: string[]) => void;
}
// const log = (config: StateCreator<Store>) => (set: SetState<Store>, get: GetState<Store>, api: StoreApi<Store>) =>
......@@ -59,5 +63,13 @@ export const useStore = create<Store>(
setNotification: (notification) => {
set({ notification });
},
filterTags: [],
setFilterTags: (tags) => {
set({ filterTags: tags });
},
filterCategories: [],
setFilterCategories: (categories) => {
set({ filterCategories: categories });
},
})),
);
......@@ -55,7 +55,7 @@ code {
}
}
.form-input {
.form-input-custom {
@apply mt-1 block w-full rounded-lg border-2 border-black border-opacity-20 hover:border-opacity-40 focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50;
}
......
import { request } from 'graphql-request';
import type { Variables } from 'graphql-request/dist/types';
const fetcher = (query: string, variables?: Record<string, unknown>): Promise<unknown> =>
const fetcher = <T, V extends Variables>(query: string, variables?: V): Promise<T> =>
request(import.meta.env.SNOWPACK_PUBLIC_API_URL, query, variables);
export default fetcher;
......@@ -8,5 +8,9 @@ module.exports = {
variants: {
extend: {},
},
plugins: [require('@tailwindcss/forms')],
plugins: [
require('@tailwindcss/forms')({
strategy: 'class',
}),
],
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment