@imerss/inat-components 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -0
- package/declaration.d.ts +2 -0
- package/dist/index.d.ts +214 -0
- package/dist/index.js +876 -0
- package/package.json +53 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +20 -0
- package/public/logo192.png +0 -0
- package/public/logo512.png +0 -0
- package/public/manifest.json +25 -0
- package/public/robots.txt +3 -0
- package/rollup.config.js +36 -0
- package/src/__shared/api/common-taxa.ts +56 -0
- package/src/__shared/api/favourites.ts +61 -0
- package/src/__shared/api/index.ts +4 -0
- package/src/__shared/api/recent-observations.ts +43 -0
- package/src/__shared/api/summary.ts +84 -0
- package/src/__shared/constants.ts +4 -0
- package/src/__shared/index.ts +4 -0
- package/src/__shared/typings.ts +69 -0
- package/src/__shared/utils/configUtils.ts +21 -0
- package/src/__shared/utils/dateUtils.ts +14 -0
- package/src/__shared/utils/index.ts +3 -0
- package/src/__shared/utils/numberUtils.ts +1 -0
- package/src/components/common-taxa/__tests__/common-taxa.test.tsx +14 -0
- package/src/components/common-taxa/common-taxa.tsx +99 -0
- package/src/components/error/__tests__/error.test.tsx +9 -0
- package/src/components/error/error.module.scss +7 -0
- package/src/components/error/error.tsx +15 -0
- package/src/components/favourites/__tests__/favourites.test.tsx +13 -0
- package/src/components/favourites/favourites.tsx +98 -0
- package/src/components/hooks/hooks.tsx +24 -0
- package/src/components/loader/__tests__/loader.test.tsx +10 -0
- package/src/components/loader/loader.tsx +26 -0
- package/src/components/no-results/__tests__/no-results.test.tsx +9 -0
- package/src/components/no-results/no-results.tsx +9 -0
- package/src/components/observation/__tests__/observation.test.tsx +14 -0
- package/src/components/observation/observation.module.scss +20 -0
- package/src/components/observation/observation.tsx +29 -0
- package/src/components/recent-observations/__tests__/recent-observations.test.tsx +9 -0
- package/src/components/recent-observations/recent-observations.tsx +101 -0
- package/src/components/seasonality-graph/__tests__/seasonality-graph.test.tsx +9 -0
- package/src/components/seasonality-graph/seasonality-graph.module.scss +7 -0
- package/src/components/seasonality-graph/seasonality-graph.tsx +46 -0
- package/src/components/shared/css/general.module.scss +69 -0
- package/src/components/summary/ObserverList.tsx +18 -0
- package/src/components/summary/__tests__/summary.test.tsx +18 -0
- package/src/components/summary/summary.module.scss +88 -0
- package/src/components/summary/summary.tsx +83 -0
- package/src/components/tabs/__tests__/tabs.test.tsx +16 -0
- package/src/components/tabs/tabs.module.scss +28 -0
- package/src/components/tabs/tabs.tsx +44 -0
- package/src/components/taxon-panel/__tests__/taxon-panel.test.tsx +10 -0
- package/src/components/taxon-panel/taxon-panel.module.scss +7 -0
- package/src/components/taxon-panel/taxon-panel.tsx +104 -0
- package/src/components/year-dropdown/__tests__/year-dropdown.test.tsx +14 -0
- package/src/components/year-dropdown/year-dropdown.module.scss +3 -0
- package/src/components/year-dropdown/year-dropdown.tsx +33 -0
- package/src/index.ts +15 -0
- package/tsconfig.json +6 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React, {useEffect, useState} from "react";
|
|
2
|
+
import {numberWithCommas, DataSource, getSummary, FavouritesRespData} from "../../__shared";
|
|
3
|
+
import { ObserverList } from './ObserverList';
|
|
4
|
+
import Loader from "../loader/loader";
|
|
5
|
+
import {SeasonalityGraph} from "../seasonality-graph/seasonality-graph";
|
|
6
|
+
import styles from "./summary.module.scss";
|
|
7
|
+
|
|
8
|
+
export type SummaryProps = {
|
|
9
|
+
source: DataSource;
|
|
10
|
+
taxonId: number;
|
|
11
|
+
placeId: number;
|
|
12
|
+
year: string | number;
|
|
13
|
+
data?: FavouritesRespData; /// TODO
|
|
14
|
+
dataUrl?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const Summary = ({ source, data, dataUrl, taxonId, placeId, year }: SummaryProps) => {
|
|
18
|
+
const [summaryData, setSummaryData] = useState<any>({});
|
|
19
|
+
const [loading, setLoading] = useState(false);
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (source !== DataSource.autoLoad) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (!taxonId) {
|
|
26
|
+
console.error("Please supply a `taxonId` prop for the `autoLoad` source prop option.");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (!placeId) {
|
|
30
|
+
console.error("Please supply a `placeId` prop for the `autoLoad` source prop option.");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
(async () => {
|
|
35
|
+
setLoading(true);
|
|
36
|
+
const data = await getSummary({ taxonId, placeId, year });
|
|
37
|
+
setSummaryData(data);
|
|
38
|
+
setLoading(false);
|
|
39
|
+
})();
|
|
40
|
+
}, [source, year, placeId, taxonId]);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (source !== DataSource.url) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!dataUrl) {
|
|
48
|
+
console.error("Please supply a `dataUrl` prop for the `url` source prop option.");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
(async () => {
|
|
53
|
+
setLoading(true);
|
|
54
|
+
const obs = await fetch(dataUrl);
|
|
55
|
+
const json = await obs.json();
|
|
56
|
+
setSummaryData(json);
|
|
57
|
+
setLoading(false);
|
|
58
|
+
})();
|
|
59
|
+
}, [source, dataUrl]);
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className={styles.summaryPage}>
|
|
63
|
+
<Loader loading={loading} />
|
|
64
|
+
|
|
65
|
+
<ul className={styles.countSummary}>
|
|
66
|
+
<li>Total number of observers: <b>{summaryData.observers?.totalCount ? numberWithCommas(summaryData.observers.totalCount): ""}</b></li>
|
|
67
|
+
<li>Total number of observations: <b>{summaryData.observations?.totalCount ? numberWithCommas(summaryData.observations.totalCount): ""}</b></li>
|
|
68
|
+
</ul>
|
|
69
|
+
|
|
70
|
+
<section>
|
|
71
|
+
<div className={styles.seasonalityBlock}>
|
|
72
|
+
<h1>Seasonality</h1>
|
|
73
|
+
{summaryData.seasonalityData && <SeasonalityGraph data={summaryData.seasonalityData.monthOfYear} />}
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<div className={styles.observersBlock}>
|
|
77
|
+
<h1>Top observers</h1>
|
|
78
|
+
{summaryData.observers?.top && <ObserverList observers={summaryData.observers.top} />}
|
|
79
|
+
</div>
|
|
80
|
+
</section>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {render} from '@testing-library/react';
|
|
2
|
+
import Tabs from '../tabs';
|
|
3
|
+
import {Tab} from "../../../index";
|
|
4
|
+
|
|
5
|
+
const defaultProps = {
|
|
6
|
+
selectedTab: Tab.recent,
|
|
7
|
+
onChangeTab: () => null,
|
|
8
|
+
features: {}
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
describe('Tabs', () => {
|
|
12
|
+
it('should render successfully', () => {
|
|
13
|
+
const {baseElement} = render(<Tabs {...defaultProps} />);
|
|
14
|
+
expect(baseElement).toBeTruthy();
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
.tabs {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: row;
|
|
4
|
+
list-style: none;
|
|
5
|
+
font-size: 18px;
|
|
6
|
+
padding: 0;
|
|
7
|
+
margin-bottom: 30px;
|
|
8
|
+
|
|
9
|
+
li {
|
|
10
|
+
margin: 0 20px;
|
|
11
|
+
padding: 4px 20px;
|
|
12
|
+
cursor: pointer;
|
|
13
|
+
color: #999999;
|
|
14
|
+
transition: all 0.2s ease-in-out;
|
|
15
|
+
|
|
16
|
+
&.selected, &:hover {
|
|
17
|
+
border-bottom: 5px solid #0099cc;
|
|
18
|
+
color: black;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@media screen and (max-width: 600px) {
|
|
24
|
+
.tabs li {
|
|
25
|
+
margin: 0 5px;
|
|
26
|
+
padding: 4px 6px;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {Tab} from "../../__shared";
|
|
3
|
+
import styles from "./tabs.module.scss";
|
|
4
|
+
import {useFeatureTitles} from "../hooks/hooks";
|
|
5
|
+
|
|
6
|
+
export type TabsProps = {
|
|
7
|
+
selectedTab: Tab;
|
|
8
|
+
onChangeTab: (tab: Tab) => void;
|
|
9
|
+
features: any; // TODO
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const Tabs = ({ selectedTab, onChangeTab, features }: TabsProps) => {
|
|
13
|
+
const titles = useFeatureTitles(features);
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<ul className={styles.tabs}>
|
|
17
|
+
{features.recentObservations && (
|
|
18
|
+
<li onClick={() => onChangeTab(Tab.recent)}
|
|
19
|
+
className={selectedTab === Tab.recent ? styles.selected : ""}>
|
|
20
|
+
{titles[Tab.recent]}
|
|
21
|
+
</li>
|
|
22
|
+
)}
|
|
23
|
+
{features.commonTaxa && (
|
|
24
|
+
<li onClick={() => onChangeTab(Tab.commonTaxa)}
|
|
25
|
+
className={selectedTab === Tab.commonTaxa ? styles.selected : ""}>
|
|
26
|
+
{titles[Tab.commonTaxa]}
|
|
27
|
+
</li>
|
|
28
|
+
)}
|
|
29
|
+
{features.favourites && (
|
|
30
|
+
<li onClick={() => onChangeTab(Tab.favourites)}
|
|
31
|
+
className={selectedTab === Tab.favourites ? styles.selected : ""}>
|
|
32
|
+
{titles[Tab.favourites]}
|
|
33
|
+
</li>
|
|
34
|
+
)}
|
|
35
|
+
{features.stats && (
|
|
36
|
+
<li onClick={() => onChangeTab(Tab.stats)} className={selectedTab === Tab.stats ? styles.selected : ""}>
|
|
37
|
+
{titles[Tab.stats]}
|
|
38
|
+
</li>
|
|
39
|
+
)}
|
|
40
|
+
</ul>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default Tabs;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {render} from '@testing-library/react';
|
|
2
|
+
|
|
3
|
+
import TaxonPanel from '../taxon-panel';
|
|
4
|
+
|
|
5
|
+
describe('TaxonPanel', () => {
|
|
6
|
+
it('should render successfully', () => {
|
|
7
|
+
const {baseElement} = render(<TaxonPanel />);
|
|
8
|
+
expect(baseElement).toBeTruthy();
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import React, {useState} from "react";
|
|
2
|
+
import {getSourceFile, Tab, DataSource, Feature} from "../../__shared";
|
|
3
|
+
import {RecentObservations, RecentObservationsProps} from '../recent-observations/recent-observations';
|
|
4
|
+
import {CommonTaxa} from '../common-taxa/common-taxa';
|
|
5
|
+
import {Favourites} from "../favourites/favourites"
|
|
6
|
+
import {Summary} from "../summary/summary";
|
|
7
|
+
import Years from "../year-dropdown/year-dropdown";
|
|
8
|
+
import Tabs from "../tabs/tabs";
|
|
9
|
+
import {useFeatureTitles} from "../hooks/hooks";
|
|
10
|
+
import styles from '../shared/css/general.module.scss';
|
|
11
|
+
|
|
12
|
+
const TaxonPanel = ({ taxonId, placeId, dataSource, config, baseURL }: any) => {
|
|
13
|
+
const [tab, setTab] = useState(Tab.recent);
|
|
14
|
+
const [year, setYear] = useState<string>("all");
|
|
15
|
+
const titles = useFeatureTitles(config.features);
|
|
16
|
+
const [taxonInfo, setTaxonInfo] = useState(() => config.taxa.find((i) => i.taxonId === taxonId));
|
|
17
|
+
const [placeInfo, setPlaceInfo] = useState(() => config.places.find((i) => i.placeId === placeId));
|
|
18
|
+
|
|
19
|
+
const getCurrentTab = () => {
|
|
20
|
+
switch (tab) {
|
|
21
|
+
case Tab.recent: {
|
|
22
|
+
const props: Partial<RecentObservationsProps> = {
|
|
23
|
+
source: dataSource
|
|
24
|
+
};
|
|
25
|
+
if (dataSource === DataSource.url) {
|
|
26
|
+
props.dataUrl = baseURL + '/' + getSourceFile(Feature.recentObservations, taxonInfo, placeInfo);
|
|
27
|
+
} else {
|
|
28
|
+
props.placeId = placeId;
|
|
29
|
+
props.taxonId = taxonId;
|
|
30
|
+
}
|
|
31
|
+
return (
|
|
32
|
+
<RecentObservations {...props} />
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
case Tab.commonTaxa: {
|
|
37
|
+
const props: any = {
|
|
38
|
+
source: dataSource,
|
|
39
|
+
year
|
|
40
|
+
};
|
|
41
|
+
if (dataSource === DataSource.url) {
|
|
42
|
+
props.dataUrl = baseURL + '/' + getSourceFile(Feature.commonTaxa, taxonInfo, placeInfo, year);
|
|
43
|
+
} else {
|
|
44
|
+
props.placeId = placeId;
|
|
45
|
+
props.taxonId = taxonId;
|
|
46
|
+
}
|
|
47
|
+
return (
|
|
48
|
+
<CommonTaxa {...props} />
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
case Tab.favourites: {
|
|
53
|
+
const props: any = {
|
|
54
|
+
source: dataSource,
|
|
55
|
+
year
|
|
56
|
+
};
|
|
57
|
+
if (dataSource === DataSource.url) {
|
|
58
|
+
props.dataUrl = baseURL + '/' + getSourceFile(Feature.favourites, taxonInfo, placeInfo, year);
|
|
59
|
+
} else {
|
|
60
|
+
props.placeId = placeId;
|
|
61
|
+
props.taxonId = taxonId;
|
|
62
|
+
}
|
|
63
|
+
return (
|
|
64
|
+
<Favourites {...props} />
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
case Tab.stats: {
|
|
69
|
+
const props: any = {
|
|
70
|
+
source: dataSource,
|
|
71
|
+
year
|
|
72
|
+
};
|
|
73
|
+
if (dataSource === DataSource.url) {
|
|
74
|
+
props.dataUrl = baseURL + '/' + getSourceFile(Feature.stats, taxonInfo, placeInfo, year);
|
|
75
|
+
} else {
|
|
76
|
+
props.placeId = placeId;
|
|
77
|
+
props.taxonId = taxonId;
|
|
78
|
+
}
|
|
79
|
+
return <Summary {...props} />;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div className={styles.page}>
|
|
86
|
+
<Tabs
|
|
87
|
+
selectedTab={tab}
|
|
88
|
+
onChangeTab={setTab}
|
|
89
|
+
features={config.features}
|
|
90
|
+
/>
|
|
91
|
+
<div>
|
|
92
|
+
{tab !== Tab.recent && (
|
|
93
|
+
<div style={{ float: "right" }}>
|
|
94
|
+
<Years value={year} onChange={setYear} />
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
97
|
+
<h1>{titles[tab]}</h1>
|
|
98
|
+
</div>
|
|
99
|
+
{getCurrentTab()}
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export default TaxonPanel;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {render} from '@testing-library/react';
|
|
2
|
+
import YearDropdown from '../year-dropdown';
|
|
3
|
+
|
|
4
|
+
const defaultProps = {
|
|
5
|
+
value: "1",
|
|
6
|
+
onChange: () => null
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
describe('YearDropdown', () => {
|
|
10
|
+
it('should render successfully', () => {
|
|
11
|
+
const {baseElement} = render(<YearDropdown {...defaultProps} />);
|
|
12
|
+
expect(baseElement).toBeTruthy();
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React, {useState} from "react";
|
|
2
|
+
import {getCurrentYear} from "../../__shared";
|
|
3
|
+
import styles from "./year-dropdown.module.scss";
|
|
4
|
+
|
|
5
|
+
interface YearsProps {
|
|
6
|
+
value: string;
|
|
7
|
+
onChange: (year: string) => void;
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const Years = ({ value, onChange, className }: YearsProps) => {
|
|
12
|
+
const [years] = useState(() => {
|
|
13
|
+
const currentYear = getCurrentYear();
|
|
14
|
+
const years: any = [];
|
|
15
|
+
for (let i = currentYear; i>=currentYear-10; i--) {
|
|
16
|
+
years.push(i);
|
|
17
|
+
}
|
|
18
|
+
return years;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
let classes = styles.dropdown;
|
|
22
|
+
if (className) {
|
|
23
|
+
classes += ` ${className}`;
|
|
24
|
+
}
|
|
25
|
+
return (
|
|
26
|
+
<select className={classes} onChange={(e) => onChange(e.target.value)} defaultValue={value}>
|
|
27
|
+
<option value="all">All years</option>
|
|
28
|
+
{years.map((year) => <option value={year} key={year}>{year}</option>)}
|
|
29
|
+
</select>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default Years;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import TaxonPanel from "./components/taxon-panel/taxon-panel";
|
|
2
|
+
|
|
3
|
+
// front-end components
|
|
4
|
+
export * from "./components/observation/observation";
|
|
5
|
+
export * from "./components/common-taxa/common-taxa";
|
|
6
|
+
export * from "./components/recent-observations/recent-observations";
|
|
7
|
+
export * from "./components/favourites/favourites";
|
|
8
|
+
export * from "./components/taxon-panel/taxon-panel";
|
|
9
|
+
export * from "./components/seasonality-graph/seasonality-graph";
|
|
10
|
+
export * from "./components/summary/summary";
|
|
11
|
+
|
|
12
|
+
// all typings
|
|
13
|
+
export * from "./__shared";
|
|
14
|
+
|
|
15
|
+
export default TaxonPanel;
|