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

Merge branch 'feat/styling-updates' into 'develop'

Feat/styling updates

See merge request software/fabcity-map/fabcity-map-frontend!17
parents a515cd76 2be0bf68
No related branches found
No related tags found
No related merge requests found
......@@ -6,8 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Fab City Hamburg Map</title>
<!-- TODO: This should be imported from the leaflet npm package, not manually from the public folder. See https://github.com/snowpackjs/snowpack/discussions/1716 -->
<link rel="stylesheet" href="leaflet.css" />
<script src="leaflet.js"></script>
<link rel="stylesheet" href="/leaflet.css" />
<script src="/leaflet.js"></script>
</head>
<body class="h-full w-full">
<div id="root" class="h-full w-full"></div>
......
......@@ -7,6 +7,7 @@ import Map from './Map/Map';
import SidebarCreateView from './Sidebar/SidebarCreateView';
import SidebarListView from './Sidebar/SidebarListView';
import SidebarSingleView from './Sidebar/SidebarSingleView';
import PoiLoader from './PoiLoader';
const App: React.FC = () => {
const selectedPoi = useStore((state) => state.selectedPoi);
......@@ -17,6 +18,10 @@ const App: React.FC = () => {
<Notification />
<div className={'flex md:flex-row-reverse flex-col h-full'}>
<Route path="/add">{({ match }) => <Map createMode={!!match} />}</Route>
<Route path="/poi/:poiId">{({ match }) => <PoiLoader poiId={match?.params?.poiId as string} />}</Route>
<Route exact path="/">
<PoiLoader poiId={null} />
</Route>
<Switch>
<Route exact path="/add">
<SidebarCreateView />
......
......@@ -6,6 +6,7 @@ import { useStore } from '../../hooks';
import MapViewController from './MapViewController';
import { useFilteredPoiData } from '../../hooks/useFilteredPoiData';
import MapLayerControl from './MapLayerControl';
import { useHistory } from 'react-router-dom';
interface Props {
createMode?: boolean;
......@@ -32,7 +33,7 @@ export const Map: React.FC<Props> = ({ createMode }) => {
const hoveredPoi = useStore((state) => state.hoveredPoi);
const setHoveredPoi = useStore((state) => state.setHoveredPoi);
const selectedPoi = useStore((state) => state.selectedPoi);
const setSelectedPoi = useStore((state) => state.setSelectedPoi);
const history = useHistory();
const selectedLatlng: LatLngTuple | undefined = selectedPoi ? [selectedPoi?.lat, selectedPoi?.lng] : undefined;
return (
......@@ -71,7 +72,9 @@ export const Map: React.FC<Props> = ({ createMode }) => {
key={poi.id}
position={poiLatLng}
eventHandlers={{
click: () => setSelectedPoi(poi),
click: () => {
history.push(`/poi/${String(poi.id)}`);
},
mouseover: () => {
setHoveredPoi(poi);
},
......
import React from 'react';
import React, { useState } from 'react';
import { useFilteredPoiData } from '../../hooks/useFilteredPoiData';
import { usePoiData } from '../../hooks';
import { ChevronDownOutline as DownIcon } from 'heroicons-react';
const MapLayerControl: React.FC = () => {
const { data } = usePoiData();
const { filterCategories, setFilterCategories } = useFilteredPoiData();
const layers = Array.from(new Set(data?.map((poi) => poi.category)));
const [isOpen, setIsOpen] = useState(false);
const onChangeCheckbox = (state: boolean, layer: string) => {
if (state) {
......@@ -16,22 +18,29 @@ const MapLayerControl: React.FC = () => {
};
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 className="border-2 border-black w-48 border-opacity-20 rounded-lg absolute right-0 bottom-0 mb-4 mr-4 bg-white p-4 z-10">
<div className={`flex justify-between cursor-pointer ${isOpen ? 'mb-2' : ''}`} onClick={() => setIsOpen(!isOpen)}>
<h3 className="text-base font-semibold text-gray-900">Kategorien:</h3>
<DownIcon
className={`w-6 h-6 text-gray-400 transform transition duration-300 ${isOpen ? 'rotate-180' : 'rotate-0'}`}
/>
</div>
{isOpen &&
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>
);
};
......
import React, { useEffect } from 'react';
import { usePoiData, useStore } from '../hooks';
interface Props {
poiId: string | null;
}
const PoiLoader: React.FC<Props> = ({ poiId }) => {
const { data } = usePoiData();
const setSelectedPoi = useStore((state) => state.setSelectedPoi);
useEffect(() => {
if (data && poiId !== undefined) {
const poi = poiId ? data.find((poi) => String(poi.id) === poiId) : null;
if (poi !== undefined) setSelectedPoi(poi);
}
}, [data, poiId]);
return <></>;
};
export default PoiLoader;
......@@ -24,6 +24,7 @@ const Select = <OptionType, isMulti extends boolean>(props: NamedProps<OptionTyp
primary25: '#C7D2FE',
},
})}
placeholder={'Tags auswählen...'}
styles={customStyles}
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"
......
import React, { SyntheticEvent } from 'react';
import { X as CloseIcon } from 'heroicons-react';
interface Props {
onClick: (event: SyntheticEvent) => void;
absolute?: boolean;
}
const CloseButton: React.FC<Props> = ({ onClick, absolute = false }) => {
return (
<CloseIcon
size={32}
className={`${
absolute ? 'absolute left-5 top-5' : ''
} p-1 text-gray-600 inline-block cursor-pointer bg-gray-200 bg-opacity-30 hover:bg-opacity-80 rounded-full`}
onClick={onClick}
/>
);
};
export default CloseButton;
import { X as CloseIcon } from 'heroicons-react';
import React from 'react';
import { useHistory } from 'react-router-dom';
import AddPoiForm from '../Form/AddPoiForm';
import CloseButton from './CloseButton';
import SidebarContainer from './SidebarContainer';
const SidebarCreateView: React.FC = () => {
const history = useHistory();
return (
<SidebarContainer className="p-5">
<CloseIcon
size={32}
className={`flex-shrink-0 left-5 top-5 p-1 text-gray-500 inline-block cursor-pointer hover:bg-gray-300 hover:bg-opacity-50 rounded-full`}
onClick={() => history.push('/')}
/>
<CloseButton onClick={() => history.push('/')} />
<h1 className="text-xl font-medium title-font text-gray-900 mt-2 mb-4">Neuen Ort anlegen:</h1>
<AddPoiForm />
</SidebarContainer>
......
import React from 'react';
import React, { useState } 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 { FilterOutline as FilterIcon } from 'heroicons-react';
import { useHistory } from 'react-router-dom';
import Select from '../Select';
const SidebarListView: React.FC = () => {
const tagsToSelectOptions = (tags?: Tag[]) => tags?.map((tag) => ({ label: tag.displayName, value: tag }));
const history = useHistory();
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 [filterInputIsOpen, setFilterInputIsOpen] = useState(false);
const tags =
data &&
......@@ -26,21 +29,35 @@ const SidebarListView: React.FC = () => {
return (
<SidebarContainer>
<div className="p-4">
<Select
options={options}
isMulti={true}
value={filterTags && tagsToSelectOptions(filterTags)}
onChange={(selectedOptions) => setFilterTags(selectedOptions.map((opt) => opt.value))}
/>
<div className="flex-col m-4 mb-2 pb-2 border-black border-opacity-20 border-b-2">
<div className="flex justify-between items-center">
<h1 className="text-xl font-medium title-font text-gray-900">{filteredData?.length} Orte:</h1>
<FilterIcon
onClick={() => setFilterInputIsOpen(!filterInputIsOpen)}
className="text-black text-opacity-20 hover:text-opacity-60 w-5 h-5 cursor-pointer"
/>
</div>
{filterInputIsOpen && (
<div className="py-2">
<Select
options={options}
isMulti={true}
value={filterTags && tagsToSelectOptions(filterTags)}
onChange={(selectedOptions) => setFilterTags(selectedOptions.map((opt) => opt.value))}
/>
</div>
)}
</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)}
onClick={() => {
history.push(`/poi/${String(poi.id)}`);
}}
value={poi}
hovered={hoveredPoi?.id === poi.id}
/>
......
import { HomeOutline as HomeIcon, LocationMarkerOutline as AddressIcon, X as CloseIcon } from 'heroicons-react';
import { HomeOutline as HomeIcon, LocationMarkerOutline as AddressIcon } from 'heroicons-react';
import React from 'react';
import { useStore } from '../../hooks';
import SidebarContainer from './SidebarContainer';
import Tag from '../Tag';
import CloseButton from './CloseButton';
import { useHistory } from 'react-router-dom';
const SidebarSingleView: React.FC = () => {
const selectedPoi = useStore((state) => state.selectedPoi);
const setSelectedPoi = useStore((state) => state.setSelectedPoi);
const strippedUrl = selectedPoi?.website?.replace(/(^\w+:|^)\/\//, '');
const history = useHistory();
return (
<SidebarContainer className={`relative p-0`}>
<div className={`${selectedPoi?.image ? '' : 'pl-5 pt-5 '}`}>
<CloseIcon
size={32}
className={`${
selectedPoi?.image ? 'absolute left-5 top-5 ' : ''
}p-1 text-gray-500 inline-block cursor-pointer hover:bg-gray-300 hover:bg-opacity-50 rounded-full`}
onClick={() => setSelectedPoi(null)}
<CloseButton
absolute
onClick={() => {
history.push('/');
}}
/>
</div>
{selectedPoi?.image && (
......@@ -36,7 +37,12 @@ const SidebarSingleView: React.FC = () => {
{selectedPoi?.website && (
<div className={'flex items-center'}>
<HomeIcon size={18} className={'text-gray-500 mr-2'} />
<a className={'text-sm text-gray-500 hover:underline'} href={selectedPoi?.website}>
<a
target="_blank"
rel="noopener noreferrer"
className={'text-sm text-gray-500 hover:underline'}
href={selectedPoi?.website}
>
{strippedUrl}
</a>
</div>
......
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