diff --git a/package-lock.json b/package-lock.json
index 7187451f..c17aa0ed 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3432,6 +3432,16 @@
"@types/react": "*"
}
},
+ "@types/react-leaflet": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/@types/react-leaflet/-/react-leaflet-2.5.2.tgz",
+ "integrity": "sha512-XBNFsBm4wQiz6BpzUMJAMXru+h2ESyW/mAdPoSzEirtF/g0NOwbUvPYnZtpbiW70AEXT40ZONtsu3lxwr/yliA==",
+ "dev": true,
+ "requires": {
+ "@types/leaflet": "*",
+ "@types/react": "*"
+ }
+ },
"@types/react-redux": {
"version": "7.1.9",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.9.tgz",
diff --git a/package.json b/package.json
index 32b42d36..f53e98e2 100644
--- a/package.json
+++ b/package.json
@@ -88,6 +88,7 @@
"@types/react-copy-to-clipboard": "^4.3.0",
"@types/react-datepicker": "~1.8.0",
"@types/react-dom": "^16.9.8",
+ "@types/react-leaflet": "^2.5.2",
"@types/react-redux": "^7.1.9",
"@types/react-router-dom": "^5.1.5",
"@types/react-tagsinput": "^3.19.7",
diff --git a/src/visits/VisitsStats.js b/src/visits/VisitsStats.js
index dc4d539d..544c36dc 100644
--- a/src/visits/VisitsStats.js
+++ b/src/visits/VisitsStats.js
@@ -14,6 +14,7 @@ import GraphCard from './helpers/GraphCard';
import LineChartCard from './helpers/LineChartCard';
import VisitsTable from './VisitsTable';
import { VisitsInfoType } from './types';
+import OpenMapModalBtn from './helpers/OpenMapModalBtn';
const propTypes = {
children: PropTypes.node,
@@ -35,7 +36,7 @@ const highlightedVisitsToStats = (highlightedVisits, prop) => highlightedVisits.
const format = formatDate();
let selectedBar;
-const VisitsStats = ({ processStatsFromVisits, normalizeVisits }, OpenMapModalBtn) => {
+const VisitsStats = ({ processStatsFromVisits, normalizeVisits }) => {
const VisitsStatsComp = ({ children, visitsInfo, getVisits, cancelGetVisits, matchMedia = window.matchMedia }) => {
const [ startDate, setStartDate ] = useState(undefined);
const [ endDate, setEndDate ] = useState(undefined);
diff --git a/src/visits/helpers/MapModal.scss b/src/visits/helpers/MapModal.scss
index d37029c6..bcf0d938 100644
--- a/src/visits/helpers/MapModal.scss
+++ b/src/visits/helpers/MapModal.scss
@@ -1,7 +1,7 @@
@import '../../utils/base';
@import '../../utils/mixins/fit-with-margin';
-.map-modal__modal {
+.map-modal__modal.map-modal__modal {
@media (min-width: $mdMin) {
$margin: 20px;
@@ -15,11 +15,11 @@
}
}
-.map-modal__modal-content {
+.map-modal__modal-content.map-modal__modal-content {
height: 100%;
}
-.map-modal__modal-title {
+.map-modal__modal-title.map-modal__modal-title {
position: absolute;
width: 100%;
z-index: 1001;
@@ -29,17 +29,17 @@
background: linear-gradient(rgba(0, 0, 0, .5), rgba(0, 0, 0, 0));
}
-.map-modal__modal-body {
+.map-modal__modal-body.map-modal__modal-body {
padding: 0;
display: flex;
overflow: hidden;
}
-.map-modal__modal .leaflet-container {
+.map-modal__modal.map-modal__modal .leaflet-container.leaflet-container {
flex: 1 1 auto;
border-radius: .3rem;
}
-.map-modal__modal .leaflet-top .leaflet-control {
+.map-modal__modal.map-modal__modal .leaflet-top.leaflet-top .leaflet-control.leaflet-control {
margin-top: 60px;
}
diff --git a/src/visits/helpers/MapModal.js b/src/visits/helpers/MapModal.tsx
similarity index 65%
rename from src/visits/helpers/MapModal.js
rename to src/visits/helpers/MapModal.tsx
index 45cc7304..85ce36e6 100644
--- a/src/visits/helpers/MapModal.js
+++ b/src/visits/helpers/MapModal.tsx
@@ -1,32 +1,25 @@
-import React from 'react';
+import React, { FC } from 'react';
import { Modal, ModalBody } from 'reactstrap';
-import { Map, TileLayer, Marker, Popup } from 'react-leaflet';
+import { Map, TileLayer, Marker, Popup, MapProps } from 'react-leaflet';
import { prop } from 'ramda';
-import * as PropTypes from 'prop-types';
+import { CityStats } from '../types';
import './MapModal.scss';
-const propTypes = {
- toggle: PropTypes.func,
- isOpen: PropTypes.bool,
- title: PropTypes.string,
- locations: PropTypes.arrayOf(PropTypes.shape({
- cityName: PropTypes.string.isRequired,
- latLong: PropTypes.arrayOf(PropTypes.number).isRequired,
- count: PropTypes.number.isRequired,
- })),
-};
-const defaultProps = {
- locations: [],
-};
+interface MapModalProps {
+ toggle: () => void;
+ isOpen: boolean;
+ title: string;
+ locations?: CityStats[];
+}
-const OpenStreetMapTile = () => (
+const OpenStreetMapTile: FC = () => (
);
-const calculateMapProps = (locations) => {
+const calculateMapProps = (locations: CityStats[]): Partial => {
if (locations.length === 0) {
return {};
}
@@ -39,10 +32,10 @@ const calculateMapProps = (locations) => {
// When that happens, we use zoom and center as a workaround
const [{ latLong: center }] = locations;
- return { zoom: '10', center };
+ return { zoom: 10, center };
};
-const MapModal = ({ toggle, isOpen, title, locations }) => (
+const MapModal = ({ toggle, isOpen, title, locations = [] }: MapModalProps) => (
@@ -61,7 +54,4 @@ const MapModal = ({ toggle, isOpen, title, locations }) => (
);
-MapModal.propTypes = propTypes;
-MapModal.defaultProps = defaultProps;
-
export default MapModal;
diff --git a/src/visits/helpers/OpenMapModalBtn.js b/src/visits/helpers/OpenMapModalBtn.js
deleted file mode 100644
index fab798c3..00000000
--- a/src/visits/helpers/OpenMapModalBtn.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import React, { useState } from 'react';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faMapMarkedAlt as mapIcon } from '@fortawesome/free-solid-svg-icons';
-import { Dropdown, DropdownItem, DropdownMenu, UncontrolledTooltip } from 'reactstrap';
-import * as PropTypes from 'prop-types';
-import { useToggle } from '../../utils/helpers/hooks';
-import './OpenMapModalBtn.scss';
-
-const propTypes = {
- modalTitle: PropTypes.string.isRequired,
- locations: PropTypes.arrayOf(PropTypes.object),
- activeCities: PropTypes.arrayOf(PropTypes.string),
-};
-
-const OpenMapModalBtn = (MapModal) => {
- const OpenMapModalBtn = ({ modalTitle, locations = [], activeCities }) => {
- const [ mapIsOpened, , openMap, closeMap ] = useToggle();
- const [ dropdownIsOpened, toggleDropdown, openDropdown ] = useToggle();
- const [ locationsToShow, setLocationsToShow ] = useState([]);
-
- const buttonRef = React.createRef();
- const filterLocations = (locations) => locations.filter(({ cityName }) => activeCities.includes(cityName));
- const onClick = () => {
- if (!activeCities) {
- setLocationsToShow(locations);
- openMap();
-
- return;
- }
-
- openDropdown();
- };
- const openMapWithLocations = (filtered) => () => {
- setLocationsToShow(filtered ? filterLocations(locations) : locations);
- openMap();
- };
-
- return (
-
-
- buttonRef.current}>Show in map
-
-
- Show all locations
- Show locations in current page
-
-
-
-
- );
- };
-
- OpenMapModalBtn.propTypes = propTypes;
-
- return OpenMapModalBtn;
-};
-
-export default OpenMapModalBtn;
diff --git a/src/visits/helpers/OpenMapModalBtn.tsx b/src/visits/helpers/OpenMapModalBtn.tsx
new file mode 100644
index 00000000..6292ecba
--- /dev/null
+++ b/src/visits/helpers/OpenMapModalBtn.tsx
@@ -0,0 +1,55 @@
+import React, { useRef, useState } from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faMapMarkedAlt as mapIcon } from '@fortawesome/free-solid-svg-icons';
+import { Dropdown, DropdownItem, DropdownMenu, UncontrolledTooltip } from 'reactstrap';
+import { useToggle } from '../../utils/helpers/hooks';
+import { CityStats } from '../types';
+import MapModal from './MapModal';
+import './OpenMapModalBtn.scss';
+
+interface OpenMapModalBtnProps {
+ modalTitle: string;
+ activeCities: string[];
+ locations?: CityStats[];
+}
+
+const OpenMapModalBtn = ({ modalTitle, activeCities, locations = [] }: OpenMapModalBtnProps) => {
+ const [ mapIsOpened, , openMap, closeMap ] = useToggle();
+ const [ dropdownIsOpened, toggleDropdown, openDropdown ] = useToggle();
+ const [ locationsToShow, setLocationsToShow ] = useState([]);
+ const buttonRef = useRef();
+
+ const filterLocations = (cities: CityStats[]) => cities.filter(({ cityName }) => activeCities.includes(cityName));
+ const onClick = () => {
+ if (!activeCities) {
+ setLocationsToShow(locations);
+ openMap();
+
+ return;
+ }
+
+ openDropdown();
+ };
+ const openMapWithLocations = (filtered: boolean) => () => {
+ setLocationsToShow(filtered ? filterLocations(locations) : locations);
+ openMap();
+ };
+
+ return (
+
+
+ buttonRef.current) as any}>Show in map
+
+
+ Show all locations
+ Show locations in current page
+
+
+
+
+ );
+};
+
+export default OpenMapModalBtn;
diff --git a/src/visits/services/provideServices.ts b/src/visits/services/provideServices.ts
index 0d0c0b09..5880193c 100644
--- a/src/visits/services/provideServices.ts
+++ b/src/visits/services/provideServices.ts
@@ -2,7 +2,6 @@ import Bottle from 'bottlejs';
import ShortUrlVisits from '../ShortUrlVisits';
import { cancelGetShortUrlVisits, getShortUrlVisits } from '../reducers/shortUrlVisits';
import { getShortUrlDetail } from '../reducers/shortUrlDetail';
-import OpenMapModalBtn from '../helpers/OpenMapModalBtn';
import MapModal from '../helpers/MapModal';
import VisitsStats from '../VisitsStats';
import { createNewVisit } from '../reducers/visitCreation';
@@ -13,9 +12,8 @@ import * as visitsParser from './VisitsParser';
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
// Components
- bottle.serviceFactory('OpenMapModalBtn', OpenMapModalBtn, 'MapModal');
bottle.serviceFactory('MapModal', () => MapModal);
- bottle.serviceFactory('VisitsStats', VisitsStats, 'VisitsParser', 'OpenMapModalBtn');
+ bottle.serviceFactory('VisitsStats', VisitsStats, 'VisitsParser');
bottle.serviceFactory('ShortUrlVisits', ShortUrlVisits, 'VisitsStats');
bottle.decorator('ShortUrlVisits', connect(
[ 'shortUrlVisits', 'shortUrlDetail', 'mercureInfo' ],
diff --git a/test/visits/helpers/MapModal.test.js b/test/visits/helpers/MapModal.test.tsx
similarity index 81%
rename from test/visits/helpers/MapModal.test.js
rename to test/visits/helpers/MapModal.test.tsx
index 41a6a370..c58ab6fa 100644
--- a/test/visits/helpers/MapModal.test.js
+++ b/test/visits/helpers/MapModal.test.tsx
@@ -1,11 +1,12 @@
import React from 'react';
-import { shallow } from 'enzyme';
+import { shallow, ShallowWrapper } from 'enzyme';
import { Modal } from 'reactstrap';
import { Marker, Popup } from 'react-leaflet';
import MapModal from '../../../src/visits/helpers/MapModal';
+import { CityStats } from '../../../src/visits/types';
describe('', () => {
- let wrapper;
+ let wrapper: ShallowWrapper;
const toggle = () => '';
const isOpen = true;
const title = 'Foobar';
@@ -13,7 +14,7 @@ describe('', () => {
const zaragozaLong = -0.876566;
const newYorkLat = 40.730610;
const newYorkLong = -73.935242;
- const locations = [
+ const locations: CityStats[] = [
{
cityName: 'Zaragoza',
count: 54,
@@ -34,12 +35,12 @@ describe('', () => {
it('renders modal with provided props', () => {
const modal = wrapper.find(Modal);
- const headerheader = wrapper.find('.map-modal__modal-title');
+ const header = wrapper.find('.map-modal__modal-title');
expect(modal.prop('toggle')).toEqual(toggle);
expect(modal.prop('isOpen')).toEqual(isOpen);
- expect(headerheader.find('.close').prop('onClick')).toEqual(toggle);
- expect(headerheader.text()).toContain(title);
+ expect(header.find('.close').prop('onClick')).toEqual(toggle);
+ expect(header.text()).toContain(title);
});
it('renders open street map tile', () => {
diff --git a/test/visits/helpers/OpenMapModalBtn.test.js b/test/visits/helpers/OpenMapModalBtn.test.tsx
similarity index 65%
rename from test/visits/helpers/OpenMapModalBtn.test.js
rename to test/visits/helpers/OpenMapModalBtn.test.tsx
index 895d9310..6bf9f04b 100644
--- a/test/visits/helpers/OpenMapModalBtn.test.js
+++ b/test/visits/helpers/OpenMapModalBtn.test.tsx
@@ -1,30 +1,25 @@
import React from 'react';
-import { mount } from 'enzyme';
+import { shallow, ShallowWrapper } from 'enzyme';
import { Dropdown, DropdownItem, UncontrolledTooltip } from 'reactstrap';
-import createOpenMapModalBtn from '../../../src/visits/helpers/OpenMapModalBtn';
+import { Mock } from 'ts-mockery';
+import OpenMapModalBtn from '../../../src/visits/helpers/OpenMapModalBtn';
+import MapModal from '../../../src/visits/helpers/MapModal';
+import { CityStats } from '../../../src/visits/types';
describe('', () => {
- let wrapper;
+ let wrapper: ShallowWrapper;
const title = 'Foo';
const locations = [
- {
- cityName: 'foo',
- count: 30,
- },
- {
- cityName: 'bar',
- count: 45,
- },
+ Mock.of({ cityName: 'foo', count: 30 }),
+ Mock.of({ cityName: 'bar', count: 45 }),
];
- const MapModal = () => '';
- const OpenMapModalBtn = createOpenMapModalBtn(MapModal);
- const createWrapper = (activeCities) => {
- wrapper = mount();
+ const createWrapper = (activeCities: string[] = []) => {
+ wrapper = shallow();
return wrapper;
};
- afterEach(() => wrapper && wrapper.unmount());
+ afterEach(() => wrapper?.unmount());
it('renders expected content', () => {
const wrapper = createWrapper();
@@ -39,21 +34,6 @@ describe('', () => {
expect(modal).toHaveLength(1);
});
- it('sets provided props to the map', (done) => {
- const wrapper = createWrapper();
- const button = wrapper.find('.open-map-modal-btn__btn');
-
- button.simulate('click');
- setImmediate(() => {
- const modal = wrapper.find(MapModal);
-
- expect(modal.prop('title')).toEqual(title);
- expect(modal.prop('locations')).toEqual(locations);
- expect(modal.prop('isOpen')).toEqual(true);
- done();
- });
- });
-
it('opens dropdown instead of modal when a list of active cities has been provided', (done) => {
const wrapper = createWrapper([ 'bar' ]);
const button = wrapper.find('.open-map-modal-btn__btn');