Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • fcos-suite/fcos-suite-map
1 result
Show changes
import React from 'react';
import { X as CloseIcon } from 'heroicons-react';
interface Props {
children: string | JSX.Element;
color?: string;
onClick?: () => void;
onClickDelete?: () => void;
}
const Tag: React.FC<Props> = ({ children, onClick, onClickDelete, color = 'pink' }) => {
return (
<div
onClick={onClick}
className={`${
onClick ? 'cursor-pointer ' : ''
}inline-flex flex-nowrap items-center px-2 py-0.5 m-0.5 rounded-full text-sm font-medium bg-indigo-100 text-indigo-800 mr-2 whitespace-nowrap`}
style={color ? { backgroundColor: color } : {}}
>
{children}
{onClickDelete && (
<CloseIcon
onClick={onClickDelete}
className="ml-0.5 inline-block cursor-pointer text-gray-600 hover:text-gray-800"
size={12}
/>
)}
</div>
);
};
export default Tag;
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'];
relationStatus: 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'];
relationStatus: 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'];
relationStatus: 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!, $relationStatus: String!, $image: Upload!, $tagIds: [ID]) {
createPoi(
poi: {name: $name, email: $email, lat: $lat, lng: $lng, website: $website, description: $description, address: $address, category: $category, relationStatus: $relationStatus, 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 { gql } from 'graphql-request';
export const createPoi = gql`
mutation createPoiMutation(
$name: String!
$email: String!
$lat: Float!
$lng: Float!
$website: String
$description: String
$address: String!
$category: String!
$relationStatus: String!
$image: Upload!
$tagIds: [ID]
) {
createPoi(
poi: {
name: $name
email: $email
lat: $lat
lng: $lng
website: $website
description: $description
address: $address
category: $category
relationStatus: $relationStatus
image: $image
tagIds: $tagIds
}
)
}
`;
export const createTags = gql`
mutation createTagsMutation($tags: [TagInput!]!) {
createTags(tags: $tags) {
id
}
}
`;
export * from './usePoiData';
export * from './useStore';
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';
export interface poiData {
data: PointOfInterest[] | undefined;
}
export const usePoiData = (): poiData => {
const { data } = useSWR<PointsOfInterestDTO>(
`{
pois {
id
name
description
website
address
lat
lng
image
category
relationStatus
tags {
id
displayName
displayName
color
}
}
}
`,
);
return { data: data?.pois };
};
import type { Error } from 'src/types/Error';
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';
import { devtools } from 'zustand/middleware';
interface Store extends State {
selectedPoi: PointOfInterest | null;
setSelectedPoi: (poi: PointOfInterest | null) => void;
hoveredPoi: PointOfInterest | null;
setHoveredPoi: (poi: PointOfInterest | null) => void;
error: Error | null;
setError: (error: Error | null) => void;
draftPoi: LatLngTuple | null;
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>) =>
// config(
// (args) => {
// console.group('Global state changed');
// console.log('%cAction:', 'color: #00A7F7; font-weight: 700;', args);
// set(args);
// console.log('%cNext State:', 'color: #47B04B; font-weight: 700;', get());
// console.groupEnd();
// },
// get,
// api,
// );
export const useStore = create<Store>(
devtools((set) => ({
selectedPoi: null,
setSelectedPoi: (poi) => {
set({
selectedPoi: poi,
});
},
hoveredPoi: null,
setHoveredPoi: (poi) => {
set({
hoveredPoi: poi,
});
},
error: null,
setError: (error) => {
set({ error });
},
draftPoi: null,
setDraftPoi: (latLng) => {
set({
draftPoi: latLng,
});
},
notification: null,
setNotification: (notification) => {
set({ notification });
},
filterTags: [],
setFilterTags: (tags) => {
set({ filterTags: tags });
},
filterCategories: [],
setFilterCategories: (categories) => {
set({ filterCategories: categories });
},
})),
);
......@@ -20,14 +20,22 @@ code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
}
.marker {
.marker-red {
color: var(--fabcity-red);
}
.marker-green {
color: var(--fabcity-green);
}
.marker-blue {
color: var(--fabcity-blue);
}
.sidebar {
flex: 3;
flex-basis: 40%;
overflow-y: auto;
max-height: 50vh;
flex-shrink: 0;
}
.leaflet-control-container {
......@@ -36,8 +44,9 @@ code {
@screen md {
.sidebar {
flex: 1;
min-width: 250px;
flex-basis: 30vw;
min-width: 300px;
max-width: 500px;
max-height: initial;
}
......@@ -45,3 +54,25 @@ code {
display: initial;
}
}
.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;
}
.form-label {
@apply leading-none text-sm text-gray-600;
}
.form-button {
@apply mt-2 flex justify-center items-center text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded-lg text-lg disabled:opacity-50 disabled:cursor-default disabled:hover:bg-indigo-500;
}
.input-chevron {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
background-position: right 0.5rem center;
background-repeat: no-repeat;
background-size: 1.5em 1.5em;
padding-right: 2.5rem;
-webkit-print-color-adjust: exact;
color-adjust: exact;
}
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { SWRConfig } from 'swr';
import { request } from 'graphql-request';
import { BrowserRouter as Router } from 'react-router-dom';
import App from './components/App';
import SwrWrapper from './components/SwrWrapper';
import './index.css';
ReactDOM.render(
<React.StrictMode>
<SWRConfig value={{ fetcher: (query: string) => request(import.meta.env.SNOWPACK_PUBLIC_API_URL, query) }}>
<App />
</SWRConfig>
<SwrWrapper>
<Router>
<App />
</Router>
</SwrWrapper>
</React.StrictMode>,
document.getElementById('root'),
);
......
[
{
"id": 1,
"lat": 53.550359,
"lng": 9.986701,
"name": "Welcome Werkstatt e. V.",
"description": "Eine offene Stadtteilwerkstatt in Barmbek-Süd. Hier kann mit Holz, Metall und Elektronik gearbeitet werden.",
"address": "Bachstr. 98, 22083 Hamburg",
"category": "OFFENE WERKSTATT",
"website": "https://www.welcome-werkstatt.de/",
"image": "https://picsum.photos/720/400?random=1"
},
{
"id": 2,
"lat": 53.560359,
"lng": 9.976701,
"name": "Fabulous St. Pauli",
"description": "Photo booth fam kinfolk cold-pressed sriracha leggings jianbing microdosing tousled waistcoat.",
"address": "Mozartstr. 8, 22081 Hamburg",
"category": "OFFENE WERKSTATT",
"website": "http://www.fablab-hamburg.org/",
"image": "https://picsum.photos/720/400?random=2"
},
{
"id": 3,
"lat": 53.540359,
"lng": 9.996701,
"name": "HoFaLab Wilhelmsburg",
"description": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text.",
"address": "Langer-Straßen-Name. 128, 22089 Hamburg",
"category": "OFFENE WERKSTATT",
"website": "https://hofalab.de/de/home-de/",
"image": "https://picsum.photos/720/400?random=3"
},
{
"id": 4,
"lat": 53.570359,
"lng": 9.986701,
"name": "Fab City Haus",
"description": "Eine offene Stadtteilwerkstatt in Barmbek-Süd. Hier kann mit Holz, Metall und Elektronik gearbeitet werden.",
"address": "Jungfernstieg 1, 22083 Hamburg",
"category": "OFFENE WERKSTATT",
"image": "https://picsum.photos/720/400?random=4"
},
{
"id": 5,
"lat": 53.565359,
"lng": 9.966701,
"name": "Haus Drei e. V.",
"description": "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text.",
"address": "Hein-Hoyer-Allee 36, 22083 Hamburg",
"category": "OFFENE WERKSTATT",
"image": "https://picsum.photos/720/400?random=5"
}
]
export interface Error {
title: string;
message: string;
icon: string;
}
export interface Notification {
title: string;
text: string;
type: 'alert' | 'success';
}
import type { LatLngExpression } from 'leaflet';
export interface PointOfInterest {
id: number;
lat: number;
lng: number;
name: string;
description: string;
address: string;
website: string;
category?: string;
pathToBanner?: string;
}
export interface PointOfInterestBase {
lat: number;
lng: number;
name: string;
description: string;
address: string;
website: string;
category: string;
relationStatus: string;
}
export interface PointOfInterest extends PointOfInterestBase {
id: number;
image?: string;
tags: Tag[];
}
export interface PointOfInterestFormData extends PointOfInterestBase {
email: string;
image: File | null;
tags: number[];
}
export interface Tag {
id: string;
displayName: string;
color: string;
}
export interface PointsOfInterestDTO {
pois: PointOfInterest[];
}
export function removeDuplicateObjects<T>(arr: T[], key: keyof T): T[] {
const tagMap = new Map(arr.map((item: T) => [item[key], item]));
return [...tagMap.values()];
}
export function generateRandomHslColor(saturation = 100, lightness = 50, distinctValues = 20): string {
const randomColorIndex = getRandomIntInclusive(0, distinctValues);
const color = `hsl(${Math.floor(randomColorIndex * (360 / distinctValues))},${saturation}%,${lightness}%)`;
return color;
}
function getRandomIntInclusive(min: number, max: number) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1) + min);
}
import { request } from 'graphql-request';
import type { Variables } from 'graphql-request/dist/types';
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;
export const validateFile = (
file: File,
allowedSize = 5000000,
allowedExtensions = ['jpg', 'jpeg', 'png'],
): boolean => {
const { name: fileName, size: fileSize } = file;
const fileExtension = fileName.split('.').pop();
if (fileExtension && !allowedExtensions.includes(fileExtension.toLowerCase())) {
return false;
} else if (fileSize > allowedSize) {
return false;
}
return true;
};
module.exports = {
mode: 'jit',
purge: ['./src/**/*.html', './src/**/*.tsx'],
darkMode: false, // or 'media' or 'class'
theme: {
......@@ -7,5 +8,9 @@ module.exports = {
variants: {
extend: {},
},
plugins: [],
plugins: [
require('@tailwindcss/forms')({
strategy: 'class',
}),
],
};