Skip to content

Commit

Permalink
Merge pull request #246 from City-of-Turku/feature/school-accessibili…
Browse files Browse the repository at this point in the history
…ty-areas

Feature/school accessibility areas
  • Loading branch information
juhomakkonen authored May 30, 2024
2 parents f1626ab + 1cdafa8 commit 69b6144
Show file tree
Hide file tree
Showing 22 changed files with 752 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { useMap } from 'react-leaflet';
import walkingIcon from 'servicemap-ui-turku/assets/icons/icons-icon_walking_area.svg';
import walkingIconBw from 'servicemap-ui-turku/assets/icons/contrast/icons-icon_walking_area-bw.svg';
import cyclingIcon from 'servicemap-ui-turku/assets/icons/icons-icon_cycling_area.svg';
import cyclingIconBw from 'servicemap-ui-turku/assets/icons/contrast/icons-icon_cycling_area-bw.svg';
import { useMobilityPlatformContext } from '../../../context/MobilityPlatformContext';
import { useAccessibleMap } from '../../../redux/selectors/settings';
import {
isDataValid,
createIcon,
fitPolygonsToBounds,
setRender,
blueOptionsBase,
whiteOptionsBase,
greenOptionsBase,
blackOptionsBase,
} from '../utils/utils';
import { isEmbed } from '../../../utils/path';
import { StyledPopupWrapper, StyledPopupInner } from '../styled/styled';
import AccessibilityAreasContent from './components/AccessibilityAreasContent';

/**
* Displays school accessibility areas on the map in polygon format.
*/
const AccessibilityAreas = () => {
const { showAccessibilityAreas, accessibilityAreasData } = useMobilityPlatformContext();

const selectedUnit = useSelector(state => state.selectedUnit?.unit?.data);
const unitId = selectedUnit?.id;
const useContrast = useSelector(useAccessibleMap);

const url = new URL(window.location);
const embedded = isEmbed({ url: url.toString() });

const map = useMap();
const { Marker, Polygon, Popup } = global.rL;
const { icon } = global.L;

const walk = 'kävely';
const bicycle = 'pyöräily';

const blueOptions = blueOptionsBase({ weight: 5, dashArray: '12 6 3', fillOpacity: '0' });
const greenOptions = greenOptionsBase({ weight: 5, fillOpacity: '0' });
const blackOptions = blackOptionsBase({ weight: 5 });
const whiteOptionsDashed = whiteOptionsBase({
fillOpacity: '0.1',
weight: 5,
dashArray: '12 6 3',
});
const whiteOptionsSolid = whiteOptionsBase({
fillOpacity: '0.1',
weight: 5,
});

const getPathOptions = transportType => {
const isWalk = transportType.includes(walk);
const isBicycle = transportType.includes(bicycle);
if (!useContrast && isWalk) {
return blueOptions;
}
if (!useContrast && isBicycle) {
return greenOptions;
}
if (useContrast && isWalk) {
return whiteOptionsDashed;
}
if (useContrast && isBicycle) {
return whiteOptionsSolid;
}
return blackOptions;
};

const walkingAreaIcon = icon(createIcon(useContrast ? walkingIconBw : walkingIcon, true));
const cyclingAreaIcon = icon(createIcon(useContrast ? cyclingIconBw : cyclingIcon, true));

const getCorrectIcon = transportType => {
if (transportType?.includes(walk)) {
return walkingAreaIcon;
}
return cyclingAreaIcon;
};

const paramValue = url.searchParams.get('accessibility_areas') === '1';
const paramValueWalk = url.searchParams.get('accessibility_areas_walk') === '1';
const paramValueBicycle = url.searchParams.get('accessibility_areas_bicycle') === '1';
const filteredAreasWalking = accessibilityAreasData.filter(
item => item.extra?.kohde_ID === unitId && item?.extra?.kulkumuoto?.includes(walk),
);
const filteredAreasCycling = accessibilityAreasData.filter(
item => item.extra?.kohde_ID === unitId && item?.extra?.kulkumuoto?.includes(bicycle),
);
const renderAll = setRender(paramValue, embedded, showAccessibilityAreas.all, accessibilityAreasData, isDataValid);
const renderWalking = setRender(
paramValueWalk,
embedded,
showAccessibilityAreas.walking,
filteredAreasWalking,
isDataValid,
);
const renderCycling = setRender(
paramValueBicycle,
embedded,
showAccessibilityAreas.cycling,
filteredAreasCycling,
isDataValid,
);

useEffect(() => {
if (!embedded) {
fitPolygonsToBounds(renderAll, accessibilityAreasData, map);
}
}, [showAccessibilityAreas.all, accessibilityAreasData]);

useEffect(() => {
if (!embedded) {
fitPolygonsToBounds(renderWalking, filteredAreasWalking, map);
}
}, [showAccessibilityAreas.walking, filteredAreasWalking]);

useEffect(() => {
if (!embedded) {
fitPolygonsToBounds(renderCycling, filteredAreasCycling, map);
}
}, [showAccessibilityAreas.cycling, filteredAreasCycling]);

const getSingleCoordinates = data => data[0][0];

const renderMarkers = (showData, data) => (showData
? data.map(item => (
<Marker
key={item.id}
icon={getCorrectIcon(item.extra.kulkumuoto)}
position={getSingleCoordinates(item.geometry_coords)}
>
<StyledPopupWrapper>
<Popup>
<StyledPopupInner>
<AccessibilityAreasContent item={item} />
</StyledPopupInner>
</Popup>
</StyledPopupWrapper>
</Marker>
))
: null);

const renderPolygons = (showData, data) => (showData
? data.map(item => (
<Polygon key={item.id} pathOptions={getPathOptions(item.extra.kulkumuoto)} positions={item.geometry_coords}>
<StyledPopupWrapper>
<Popup>
<StyledPopupInner>
<AccessibilityAreasContent item={item} />
</StyledPopupInner>
</Popup>
</StyledPopupWrapper>
</Polygon>
))
: null);

return (
<>
{renderMarkers(renderAll, accessibilityAreasData)}
{renderMarkers(renderWalking, filteredAreasWalking)}
{renderMarkers(renderCycling, filteredAreasCycling)}
{renderPolygons(renderAll, accessibilityAreasData)}
{renderPolygons(renderWalking, filteredAreasWalking)}
{renderPolygons(renderCycling, filteredAreasCycling)}
</>
);
};

export default AccessibilityAreas;
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import PropTypes from 'prop-types';
import React from 'react';
import { Typography } from '@mui/material';
import { useIntl } from 'react-intl';
import { StyledContainer, StyledHeaderContainer, StyledTextContainer } from '../../../styled/styled';

const AccessibilityAreasContent = ({ item }) => {
const intl = useIntl();

const contentInfo = (
<StyledContainer>
<StyledHeaderContainer>
<Typography variant="subtitle1" component="h4">
{item.name_fi}
</Typography>
</StyledHeaderContainer>
<div>
<StyledTextContainer>
<Typography variant="subtitle2" component="p">
{intl.formatMessage({ id: 'unit.accessibilityAreas.content.subtitle' })}
</Typography>
</StyledTextContainer>
<StyledTextContainer>
<Typography variant="body2" component="p">
{intl.formatMessage({ id: 'unit.accessibilityAreas.content.transport' }, { value: item?.extra?.kulkumuoto })}
</Typography>
</StyledTextContainer>
<StyledTextContainer>
<Typography variant="body2" component="p">
{intl.formatMessage({ id: 'unit.accessibilityAreas.content.duration' }, { value: item?.extra?.minuutit })}
</Typography>
</StyledTextContainer>
</div>
</StyledContainer>
);

return <div>{contentInfo}</div>;
};

AccessibilityAreasContent.propTypes = {
item: PropTypes.shape({
name_fi: PropTypes.string,
extra: PropTypes.shape({
kulkumuoto: PropTypes.string,
minuutit: PropTypes.number,
}),
}),
};

AccessibilityAreasContent.defaultProps = {
item: {},
};

export default AccessibilityAreasContent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Link.react.test.js
import React from 'react';
import AccessibilityAreasContent from '../index';
import { getRenderWithProviders } from '../../../../../../../jestUtils';
import finnishTranslations from '../../../../../../i18n/fi';

const mockProps = {
item: {
extra: {
kulkumuoto: 'Kävely',
minuutit: 10,
},
},
};

const renderWithProviders = getRenderWithProviders({});

describe('<AccessibilityAreasContent />', () => {
it('should work', () => {
const { container } = renderWithProviders(<AccessibilityAreasContent {...mockProps} />);
expect(container).toMatchSnapshot();
});

it('does show text correctly', () => {
const { container } = renderWithProviders(<AccessibilityAreasContent {...mockProps} />);

const p = container.querySelectorAll('p');
expect(p[0].textContent).toContain(`${finnishTranslations['unit.accessibilityAreas.content.subtitle']}`);
expect(p[1].textContent).toContain(
`${finnishTranslations['unit.accessibilityAreas.content.transport'].replace(
'{value}',
`${mockProps.item.extra.kulkumuoto}`,
)}`,
);
expect(p[2].textContent).toContain(
`${finnishTranslations['unit.accessibilityAreas.content.duration'].replace(
'{value}',
`${mockProps.item.extra.minuutit}`,
)}`,
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`<AccessibilityAreasContent /> should work 1`] = `
<div>
<div>
<div
class="css-1ynyhby"
>
<div
class="css-1dznbe2"
>
<h4
class="MuiTypography-root MuiTypography-subtitle1 css-dlbwt1-MuiTypography-root"
/>
</div>
<div>
<div
class="css-1fhgjcy"
>
<p
class="MuiTypography-root MuiTypography-subtitle2 css-chrj9w-MuiTypography-root"
>
Lähestymisalue:
</p>
</div>
<div
class="css-1fhgjcy"
>
<p
class="MuiTypography-root MuiTypography-body2 css-1ak20dk-MuiTypography-root"
>
Kulkumuto: Kävely
</p>
</div>
<div
class="css-1fhgjcy"
>
<p
class="MuiTypography-root MuiTypography-body2 css-1ak20dk-MuiTypography-root"
>
Arvioitu aika: 10 minuuttia
</p>
</div>
</div>
</div>
</div>
</div>
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import AccessibilityAreasContent from './AccessibilityAreasContent';

export default AccessibilityAreasContent;
3 changes: 3 additions & 0 deletions src/components/MobilityPlatform/AccessibilityAreas/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import AccessibilityAreas from './AccessibilityAreas';

export default AccessibilityAreas;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Link.react.test.js
import React from 'react';
import BarbecuePlacesContent from '../BarbecuePlacesContent';
import BarbecuePlacesContent from '../index';
import { getRenderWithProviders } from '../../../../../../../jestUtils';
import finnishTranslations from '../../../../../../i18n/fi';

Expand Down
10 changes: 6 additions & 4 deletions src/components/MobilityPlatform/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,17 @@ const isDataValid = (visibilityValue, data) => visibilityValue && data && data.l
*/
const isObjValid = (visibilityValue, obj) => visibilityValue && obj && Object.entries(obj).length > 0;

const createIcon = (icon) => ({
const createIcon = (icon, isSmall) => ({
iconUrl: icon,
iconSize: [45, 45],
iconSize: isSmall ? [35, 35] : [45, 45],
});

const whiteOptionsBase = (attrs = {}) => ({ color: 'rgba(255, 255, 255, 255)', ...attrs });
const blackOptionsBase = (attrs = {}) => ({ color: 'rgba(0, 0, 0, 255)', ...attrs });
const blueOptionsBase = (attrs = {}) => ({ color: 'rgba(7, 44, 115, 255)', ...attrs });
const redOptionsBase = (attrs = {}) => ({ color: 'rgba(251, 5, 21, 255)', ...attrs });
const grayOptionsBase = (attrs = {}) => ({ color: 'rgba(64, 64, 64, 255)', ...attrs });
const greenOptionsBase = (attrs = {}) => ({ color: 'rgba(15, 115, 6, 255)', ...attrs });

/**
* Return arrays of coordinates that fit markers inside map bounds
Expand All @@ -35,7 +36,7 @@ const grayOptionsBase = (attrs = {}) => ({ color: 'rgba(64, 64, 64, 255)', ...at
const fitToMapBounds = (renderData, data, map) => {
if (renderData) {
const bounds = [];
data.forEach((item) => {
data.forEach(item => {
bounds.push([item.geometry_coords.lat, item.geometry_coords.lon]);
});
map.fitBounds(bounds);
Expand All @@ -52,7 +53,7 @@ const fitToMapBounds = (renderData, data, map) => {
const fitPolygonsToBounds = (renderData, data, map) => {
if (renderData) {
const bounds = [];
data.forEach((item) => {
data.forEach(item => {
bounds.push(item.geometry_coords);
});
map.fitBounds(bounds);
Expand Down Expand Up @@ -100,6 +101,7 @@ export {
blueOptionsBase,
redOptionsBase,
grayOptionsBase,
greenOptionsBase,
fitToMapBounds,
fitPolygonsToBounds,
setRender,
Expand Down
Loading

0 comments on commit 69b6144

Please sign in to comment.