@imerss/inat-components 1.1.1 → 2.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/PUBLISH.md +13 -0
- package/README.md +20 -0
- package/dist/index.d.ts +118 -73
- package/dist/index.js +322 -389
- package/dist/standalone.min.js +3 -0
- package/package.json +17 -5
- package/public/standalone.html +55 -0
- package/rollup.config.js +15 -4
- package/src/__shared/api/common-taxa.ts +3 -3
- package/src/__shared/api/favourites.ts +6 -11
- package/src/__shared/api/recent-observations.ts +3 -3
- package/src/__shared/api/summary.ts +1 -0
- package/src/__shared/constants.ts +3 -1
- package/src/__shared/index.ts +1 -1
- package/src/__shared/typings.ts +58 -44
- package/src/__shared/utils/configUtils.ts +10 -6
- package/src/__shared/utils/dateUtils.ts +1 -1
- package/src/components/common-taxa/common-taxa.tsx +34 -60
- package/src/components/error/error.tsx +1 -1
- package/src/components/favourites/favourites.tsx +35 -60
- package/src/components/hooks/hooks.tsx +20 -10
- package/src/components/loader/loader.tsx +1 -1
- package/src/components/observation/observation.tsx +5 -4
- package/src/components/recent-observations/recent-observations.tsx +43 -60
- package/src/components/shared/css/general.module.scss +5 -1
- package/src/components/summary/ObserverList.tsx +21 -15
- package/src/components/summary/summary.tsx +59 -51
- package/src/components/tabs/tabs.tsx +48 -27
- package/src/components/taxon-panel/taxon-panel.tsx +83 -77
- package/src/components/year-dropdown/year-dropdown.tsx +11 -7
- package/src/hooks/useLoadSourceData.ts +81 -0
- package/src/index.ts +1 -1
- package/src/types/index.d.ts +17 -0
- package/src/types/standalone.d.ts +6 -0
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import {formatDate, C, getFavourites, FavouritesData, BaseComponentProps,
|
|
3
|
-
import {Observation} from "../observation/observation";
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { formatDate, C, getFavourites, FavouritesData, BaseComponentProps, GeneralClasses } from "../../__shared";
|
|
3
|
+
import { Observation } from "../observation/observation";
|
|
4
4
|
import Loader from "../loader/loader";
|
|
5
|
-
import {NoResults} from "../no-results/no-results";
|
|
5
|
+
import { NoResults } from "../no-results/no-results";
|
|
6
|
+
import { useLoadSourceData } from "../../hooks/useLoadSourceData";
|
|
6
7
|
import generalStyles from "../shared/css/general.module.scss";
|
|
7
8
|
|
|
8
9
|
export type FavouritesProps = BaseComponentProps & {
|
|
9
|
-
year:
|
|
10
|
+
year: number | string;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
export const FavouritesLabel = (data: FavouritesData) => (
|
|
13
|
+
export const FavouritesLabel = (data: FavouritesData & { classes: GeneralClasses }) => (
|
|
13
14
|
<div className={generalStyles.obsLabel}>
|
|
14
|
-
<h3>{data.taxonCommonName || data.taxonName}</h3>
|
|
15
|
-
<div>{data.observerUsername}</div>
|
|
16
|
-
<div>{formatDate(data.obsDate)}</div>
|
|
17
|
-
<label className={generalStyles.count}>{data.numFaves}</label>
|
|
15
|
+
<h3 className={data.classes?.observationLabelTitle}>{data.taxonCommonName || data.taxonName}</h3>
|
|
16
|
+
<div className={data.classes?.observationLabelTitle}>{data.observerUsername}</div>
|
|
17
|
+
<div className={data.classes?.observationLabelDate}>{formatDate(data.obsDate)}</div>
|
|
18
|
+
<label className={`${data.classes?.observationCount || ''} ${generalStyles.count}`}>{data.numFaves}</label>
|
|
18
19
|
</div>
|
|
19
20
|
);
|
|
20
21
|
|
|
@@ -23,73 +24,47 @@ export const Favourites = ({
|
|
|
23
24
|
source,
|
|
24
25
|
taxonId,
|
|
25
26
|
placeId,
|
|
26
|
-
data,
|
|
27
27
|
dataUrl,
|
|
28
28
|
components,
|
|
29
29
|
className,
|
|
30
|
-
|
|
30
|
+
tabDesc,
|
|
31
|
+
generalClasses,
|
|
32
|
+
itemWidth = C.DEFAULT_ITEM_WIDTH,
|
|
33
|
+
numResults = C.NUM_RESULTS
|
|
31
34
|
}: FavouritesProps) => {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
if (!placeId) {
|
|
44
|
-
console.error("Please supply a `placeId` prop for the `autoLoad` source prop option.");
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
(async () => {
|
|
49
|
-
setLoading(true);
|
|
50
|
-
const resp = await getFavourites({taxonId, placeId, year, perPage});
|
|
51
|
-
setObservations(resp.results);
|
|
52
|
-
setLoading(false);
|
|
53
|
-
})();
|
|
54
|
-
}, [source, year, placeId, taxonId, perPage]);
|
|
55
|
-
|
|
56
|
-
useEffect(() => {
|
|
57
|
-
if (source !== DataSource.url) {
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (!dataUrl) {
|
|
62
|
-
console.error("Please supply a `dataUrl` prop for the `url` source prop option.");
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
(async () => {
|
|
67
|
-
setLoading(true);
|
|
68
|
-
const obs = await fetch(dataUrl);
|
|
69
|
-
const json = await obs.json();
|
|
70
|
-
setObservations(json.results);
|
|
71
|
-
setLoading(false);
|
|
72
|
-
})();
|
|
73
|
-
}, [source, dataUrl]);
|
|
74
|
-
|
|
35
|
+
const { loading, results: observations } = useLoadSourceData({
|
|
36
|
+
year,
|
|
37
|
+
taxonId,
|
|
38
|
+
placeId,
|
|
39
|
+
numResults,
|
|
40
|
+
dataUrl,
|
|
41
|
+
source,
|
|
42
|
+
action: getFavourites
|
|
43
|
+
});
|
|
75
44
|
const Load = components?.loader ? components.loader as any : Loader;
|
|
76
45
|
const Label = components?.label ? components.label as any : FavouritesLabel;
|
|
77
46
|
|
|
78
|
-
let
|
|
47
|
+
let elClasses = generalStyles.panel;
|
|
79
48
|
if (className) {
|
|
80
|
-
|
|
49
|
+
elClasses += ` ${className}`;
|
|
50
|
+
}
|
|
51
|
+
let descClasses = generalStyles.tabDesc;
|
|
52
|
+
if (generalClasses?.tabDesc) {
|
|
53
|
+
descClasses += ` ${generalClasses.tabDesc}`;
|
|
81
54
|
}
|
|
82
55
|
return (
|
|
83
|
-
<div className={
|
|
56
|
+
<div className={elClasses}>
|
|
57
|
+
{tabDesc && <p className={descClasses}>{tabDesc}</p>}
|
|
84
58
|
<Load loading={loading}/>
|
|
85
59
|
{!loading && observations.length === 0 && <NoResults/>}
|
|
86
|
-
<div className={generalStyles.grid}>
|
|
60
|
+
<div className={generalStyles.grid} style={{ gridTemplateColumns: `repeat(auto-fit, minmax(${itemWidth}px, 1fr))` }}>
|
|
87
61
|
{observations.map((obs: any) => (
|
|
88
62
|
<Observation
|
|
89
63
|
key={obs.id}
|
|
90
64
|
imageUrl={obs.imageUrl.replace(/square/, "medium")}
|
|
65
|
+
itemWidth={itemWidth}
|
|
91
66
|
linkUrl={obs.obsUrl}>
|
|
92
|
-
<Label {...obs} />
|
|
67
|
+
<Label {...obs} classes={generalClasses} />
|
|
93
68
|
</Observation>
|
|
94
69
|
))}
|
|
95
70
|
</div>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {useEffect, useState} from "react";
|
|
2
|
-
import {
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { Feature, TaxonPanelFeatures } from "../../__shared";
|
|
3
3
|
|
|
4
4
|
const defaultTitles = {
|
|
5
|
-
[
|
|
6
|
-
[
|
|
7
|
-
[
|
|
8
|
-
[
|
|
5
|
+
[Feature.recentObservations]: "Recent",
|
|
6
|
+
[Feature.commonTaxa]: "Most common",
|
|
7
|
+
[Feature.favourites]: "Most favourited",
|
|
8
|
+
[Feature.stats]: "General stats"
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
export const useFeatureTitles = (features: any) => {
|
|
@@ -13,12 +13,22 @@ export const useFeatureTitles = (features: any) => {
|
|
|
13
13
|
|
|
14
14
|
useEffect(() => {
|
|
15
15
|
setTitles({
|
|
16
|
-
[
|
|
17
|
-
[
|
|
18
|
-
[
|
|
19
|
-
[
|
|
16
|
+
[Feature.recentObservations]: features.recentObservations?.title || defaultTitles[Feature.recentObservations],
|
|
17
|
+
[Feature.commonTaxa]: features.commonTaxa?.title || defaultTitles[Feature.commonTaxa],
|
|
18
|
+
[Feature.favourites]: features.favourites?.title || defaultTitles[Feature.favourites],
|
|
19
|
+
[Feature.stats]: features.stats?.title || defaultTitles[Feature.stats]
|
|
20
20
|
});
|
|
21
21
|
}, [features]);
|
|
22
22
|
|
|
23
23
|
return titles;
|
|
24
24
|
};
|
|
25
|
+
|
|
26
|
+
export const convertFeaturesToObj = (features: TaxonPanelFeatures) => {
|
|
27
|
+
const featuresObj: any = {};
|
|
28
|
+
features.forEach((row) => {
|
|
29
|
+
const { feature, ...other } = row;
|
|
30
|
+
featuresObj[feature] = other;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return featuresObj;
|
|
34
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import LoadingSpinner from "react-spinners/MoonLoader.js";
|
|
2
|
+
import LoadingSpinner from "react-spinners/MoonLoader.js";
|
|
3
3
|
import styles from "../shared/css/general.module.scss";
|
|
4
4
|
|
|
5
5
|
export type LoaderProps = {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import React, {useCallback} from
|
|
1
|
+
import React, { useCallback } from "react";
|
|
2
2
|
import styles from "./observation.module.scss";
|
|
3
3
|
|
|
4
4
|
export type ObservationProps = {
|
|
5
5
|
imageUrl: string;
|
|
6
6
|
linkUrl: string;
|
|
7
|
+
itemWidth: number;
|
|
7
8
|
children: any;
|
|
8
9
|
};
|
|
9
10
|
|
|
10
|
-
export const Observation = ({imageUrl, linkUrl, children}: ObservationProps) => {
|
|
11
|
+
export const Observation = ({imageUrl, linkUrl, itemWidth, children}: ObservationProps) => {
|
|
11
12
|
const gotoObservation = useCallback(() => {
|
|
12
13
|
window.open(linkUrl);
|
|
13
14
|
}, [linkUrl]);
|
|
@@ -15,8 +16,8 @@ export const Observation = ({imageUrl, linkUrl, children}: ObservationProps) =>
|
|
|
15
16
|
return (
|
|
16
17
|
<article className={styles.obs} onClick={gotoObservation}>
|
|
17
18
|
<div style={{
|
|
18
|
-
width:
|
|
19
|
-
height:
|
|
19
|
+
width: itemWidth,
|
|
20
|
+
height: itemWidth,
|
|
20
21
|
overflow: "hidden",
|
|
21
22
|
borderRadius: 5,
|
|
22
23
|
background: `url(${imageUrl}) 50% 50% no-repeat`,
|
|
@@ -1,17 +1,26 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {
|
|
3
|
+
formatDate,
|
|
4
|
+
C,
|
|
5
|
+
getRecentObservations,
|
|
6
|
+
RecentObservationData,
|
|
7
|
+
BaseComponentProps,
|
|
8
|
+
DataSourceEnum,
|
|
9
|
+
GeneralClasses
|
|
10
|
+
} from "../../__shared";
|
|
11
|
+
import { Observation } from "../observation/observation";
|
|
4
12
|
import Loader from "../loader/loader";
|
|
5
|
-
import {NoResults} from "../no-results/no-results";
|
|
13
|
+
import { NoResults } from "../no-results/no-results";
|
|
14
|
+
import { useLoadSourceData } from "../../hooks/useLoadSourceData";
|
|
6
15
|
import styles from "../shared/css/general.module.scss";
|
|
7
16
|
|
|
8
17
|
export type RecentObservationsProps = BaseComponentProps;
|
|
9
18
|
|
|
10
|
-
export const RecentObservationLabel = (obs: RecentObservationData) => (
|
|
19
|
+
export const RecentObservationLabel = (obs: RecentObservationData & { classes: GeneralClasses }) => (
|
|
11
20
|
<div className={styles.obsLabel}>
|
|
12
|
-
<h3>{obs.taxonCommonName || obs.taxonName}</h3>
|
|
13
|
-
<div>{formatDate(obs.obsDate)}</div>
|
|
14
|
-
<div>
|
|
21
|
+
<h3 className={obs.classes?.observationLabelTitle}>{obs.taxonCommonName || obs.taxonName}</h3>
|
|
22
|
+
<div className={obs.classes?.observationLabelDate}>{formatDate(obs.obsDate)}</div>
|
|
23
|
+
<div className={obs.classes?.observationLabelName}>
|
|
15
24
|
<a href={`${C.BASE_URL}/people/${obs.observerUsername}`}
|
|
16
25
|
target="_blank"
|
|
17
26
|
rel="noreferrer"
|
|
@@ -25,74 +34,48 @@ export const RecentObservationLabel = (obs: RecentObservationData) => (
|
|
|
25
34
|
export const RecentObservations = ({
|
|
26
35
|
taxonId,
|
|
27
36
|
placeId,
|
|
28
|
-
data,
|
|
29
37
|
dataUrl,
|
|
30
|
-
source = DataSource.autoLoad,
|
|
31
|
-
perPage = C.PER_PAGE,
|
|
32
38
|
components,
|
|
33
|
-
className
|
|
39
|
+
className,
|
|
40
|
+
generalClasses,
|
|
41
|
+
tabDesc,
|
|
42
|
+
source = DataSourceEnum.autoLoad,
|
|
43
|
+
numResults = C.NUM_RESULTS,
|
|
44
|
+
itemWidth = C.DEFAULT_ITEM_WIDTH
|
|
34
45
|
}: RecentObservationsProps) => {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
console.error("Please supply a `taxonId` prop for the `autoLoad` source prop option.");
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
if (!placeId) {
|
|
47
|
-
console.error("Please supply a `placeId` prop for the `autoLoad` source prop option.");
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
(async () => {
|
|
52
|
-
setLoading(true);
|
|
53
|
-
const obs = await getRecentObservations({taxonId, placeId, perPage});
|
|
54
|
-
setObservations(obs.results);
|
|
55
|
-
setLoading(false);
|
|
56
|
-
})();
|
|
57
|
-
}, [source, taxonId, placeId, perPage]);
|
|
58
|
-
|
|
59
|
-
useEffect(() => {
|
|
60
|
-
if (source !== DataSource.url) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (!dataUrl) {
|
|
65
|
-
console.error("Please supply a `dataUrl` prop for the `url` source prop option.");
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
(async () => {
|
|
70
|
-
setLoading(true);
|
|
71
|
-
const obs = await fetch(dataUrl);
|
|
72
|
-
const json = await obs.json();
|
|
73
|
-
setObservations(json.results);
|
|
74
|
-
setLoading(false);
|
|
75
|
-
})();
|
|
76
|
-
}, [source, dataUrl]);
|
|
77
|
-
|
|
46
|
+
const { loading, results: observations } = useLoadSourceData({
|
|
47
|
+
taxonId,
|
|
48
|
+
placeId,
|
|
49
|
+
numResults,
|
|
50
|
+
dataUrl,
|
|
51
|
+
source,
|
|
52
|
+
action: getRecentObservations
|
|
53
|
+
});
|
|
78
54
|
const Load = components?.loader ? components.loader as any : Loader;
|
|
79
55
|
const Label = components?.label ? components.label as any : RecentObservationLabel;
|
|
80
56
|
|
|
81
|
-
let
|
|
57
|
+
let componentClasses = styles.panel;
|
|
82
58
|
if (className) {
|
|
83
|
-
|
|
59
|
+
componentClasses += ` ${className}`;
|
|
84
60
|
}
|
|
61
|
+
let descClasses = styles.tabDesc;
|
|
62
|
+
if (generalClasses?.tabDesc) {
|
|
63
|
+
descClasses += ` ${generalClasses.tabDesc}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
85
66
|
return (
|
|
86
|
-
<div className={
|
|
67
|
+
<div className={componentClasses}>
|
|
68
|
+
{tabDesc && <p className={descClasses}>{tabDesc}</p>}
|
|
87
69
|
<Load loading={loading}/>
|
|
88
|
-
<div className={styles.grid}>
|
|
70
|
+
<div className={styles.grid} style={{ gridTemplateColumns: `repeat(auto-fit, minmax(${itemWidth}px, 1fr))` }}>
|
|
89
71
|
{!loading && observations.length === 0 && <NoResults/>}
|
|
90
72
|
{observations.map((obs: RecentObservationData) => (
|
|
91
73
|
<Observation
|
|
92
74
|
key={obs.id}
|
|
93
75
|
imageUrl={obs.imageUrl.replace(/square/, "medium")}
|
|
76
|
+
itemWidth={itemWidth}
|
|
94
77
|
linkUrl={obs.obsUrl}>
|
|
95
|
-
<Label {...obs} />
|
|
78
|
+
<Label {...obs} classes={generalClasses} />
|
|
96
79
|
</Observation>
|
|
97
80
|
))}
|
|
98
81
|
</div>
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
|
|
17
17
|
.grid {
|
|
18
18
|
display: grid;
|
|
19
|
-
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr) );
|
|
20
19
|
width: 100%;
|
|
21
20
|
grid-gap: 8px;
|
|
22
21
|
text-align: center;
|
|
@@ -67,3 +66,8 @@
|
|
|
67
66
|
font-weight: bold;
|
|
68
67
|
font-size: 18px;
|
|
69
68
|
}
|
|
69
|
+
|
|
70
|
+
.tabDesc {
|
|
71
|
+
font-size: 14px;
|
|
72
|
+
margin-bottom: 20px;
|
|
73
|
+
}
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {numberWithCommas, Observer, C} from "../../__shared";
|
|
2
|
+
import { numberWithCommas, Observer, C, GeneralClasses } from "../../__shared";
|
|
3
3
|
import styles from "./summary.module.scss";
|
|
4
4
|
|
|
5
|
-
export const ObserverList = ({observers}: { observers: Observer[] }) =>
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
)
|
|
5
|
+
export const ObserverList = ({ observers, generalClasses, className = '' }: { observers: Observer[], generalClasses?: GeneralClasses, className?: string }) => {
|
|
6
|
+
let classes = styles.top;
|
|
7
|
+
if (className) {
|
|
8
|
+
classes += ` ${className}`;
|
|
9
|
+
}
|
|
10
|
+
return (
|
|
11
|
+
<ul className={classes}>
|
|
12
|
+
{observers.map((obs) => (
|
|
13
|
+
<li key={obs.id}>
|
|
14
|
+
<a href={`${C.BASE_URL}/people/${obs.userName}`} target="_blank" rel="noreferrer">
|
|
15
|
+
{obs.iconUrl ? <img src={obs.iconUrl || ""} className={styles.avatar} alt="User icon"/> :
|
|
16
|
+
<div className={styles.noAvatar}/>}
|
|
17
|
+
<h3>{obs.userName}</h3>
|
|
18
|
+
<label className={generalClasses?.observationCount}>{numberWithCommas(obs.numObservations)}</label>
|
|
19
|
+
</a>
|
|
20
|
+
</li>
|
|
21
|
+
))}
|
|
22
|
+
</ul>
|
|
23
|
+
);
|
|
24
|
+
};
|
|
@@ -1,83 +1,91 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import {numberWithCommas, DataSource, getSummary,
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { numberWithCommas, DataSource, getSummary, GeneralClasses } from "../../__shared";
|
|
3
3
|
import { ObserverList } from './ObserverList';
|
|
4
4
|
import Loader from "../loader/loader";
|
|
5
|
-
import {SeasonalityGraph} from "../seasonality-graph/seasonality-graph";
|
|
5
|
+
import { SeasonalityGraph } from "../seasonality-graph/seasonality-graph";
|
|
6
6
|
import styles from "./summary.module.scss";
|
|
7
|
+
import { useLoadSourceData } from "../../hooks/useLoadSourceData";
|
|
7
8
|
|
|
8
9
|
export type SummaryProps = {
|
|
9
|
-
source: DataSource;
|
|
10
10
|
taxonId: number;
|
|
11
11
|
placeId: number;
|
|
12
|
-
year:
|
|
13
|
-
|
|
12
|
+
year: number;
|
|
13
|
+
source: DataSource;
|
|
14
14
|
dataUrl?: string;
|
|
15
|
+
generalClasses?: GeneralClasses;
|
|
16
|
+
className?: string;
|
|
17
|
+
tabDesc?: string;
|
|
18
|
+
observersListClass?: string;
|
|
19
|
+
statsCountSummaryClass?: string;
|
|
15
20
|
};
|
|
16
21
|
|
|
17
|
-
export const Summary = ({
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
22
|
+
export const Summary = ({
|
|
23
|
+
source,
|
|
24
|
+
dataUrl,
|
|
25
|
+
taxonId,
|
|
26
|
+
placeId,
|
|
27
|
+
year,
|
|
28
|
+
generalClasses,
|
|
29
|
+
className,
|
|
30
|
+
tabDesc,
|
|
31
|
+
observersListClass,
|
|
32
|
+
statsCountSummaryClass
|
|
33
|
+
}: SummaryProps) => {
|
|
34
|
+
const { loading, results: summaryData } = useLoadSourceData({
|
|
35
|
+
year,
|
|
36
|
+
taxonId,
|
|
37
|
+
placeId,
|
|
38
|
+
dataUrl,
|
|
39
|
+
source,
|
|
40
|
+
isSummaryData: true,
|
|
33
41
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
setLoading(false);
|
|
39
|
-
})();
|
|
40
|
-
}, [source, year, placeId, taxonId]);
|
|
42
|
+
// shim to standardize the response from the `action` method with a top-level `results` property. This
|
|
43
|
+
// just lets the useLoadSourceData hook work for all tab components
|
|
44
|
+
action: async (data) => {
|
|
45
|
+
const results = await getSummary(data);
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
if (source !== DataSource.url) {
|
|
44
|
-
return;
|
|
47
|
+
return { results };
|
|
45
48
|
}
|
|
49
|
+
});
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
let countSummaryClasses = styles.countSummary;
|
|
52
|
+
if (statsCountSummaryClass) {
|
|
53
|
+
countSummaryClasses += ` ${statsCountSummaryClass}`;
|
|
54
|
+
}
|
|
55
|
+
let descClasses = styles.tabDesc;
|
|
56
|
+
if (generalClasses?.tabDesc) {
|
|
57
|
+
descClasses += ` ${generalClasses.tabDesc}`;
|
|
58
|
+
}
|
|
51
59
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
setSummaryData(json);
|
|
57
|
-
setLoading(false);
|
|
58
|
-
})();
|
|
59
|
-
}, [source, dataUrl]);
|
|
60
|
+
let classList = styles.summaryPage;
|
|
61
|
+
if (className) {
|
|
62
|
+
classList += ` ${className}`;
|
|
63
|
+
}
|
|
60
64
|
|
|
61
65
|
return (
|
|
62
|
-
<div className={
|
|
66
|
+
<div className={classList}>
|
|
67
|
+
{tabDesc && <p className={descClasses}>{tabDesc}</p>}
|
|
63
68
|
<Loader loading={loading} />
|
|
64
|
-
|
|
65
|
-
<ul className={styles.countSummary}>
|
|
69
|
+
<ul className={countSummaryClasses}>
|
|
66
70
|
<li>Total number of observers: <b>{summaryData.observers?.totalCount ? numberWithCommas(summaryData.observers.totalCount): ""}</b></li>
|
|
67
71
|
<li>Total number of observations: <b>{summaryData.observations?.totalCount ? numberWithCommas(summaryData.observations.totalCount): ""}</b></li>
|
|
68
72
|
</ul>
|
|
69
|
-
|
|
70
73
|
<section>
|
|
71
74
|
<div className={styles.seasonalityBlock}>
|
|
72
75
|
<h1>Seasonality</h1>
|
|
73
76
|
{summaryData.seasonalityData && <SeasonalityGraph data={summaryData.seasonalityData.monthOfYear} />}
|
|
74
77
|
</div>
|
|
75
|
-
|
|
76
78
|
<div className={styles.observersBlock}>
|
|
77
79
|
<h1>Top observers</h1>
|
|
78
|
-
{summaryData.observers?.top &&
|
|
80
|
+
{summaryData.observers?.top && (
|
|
81
|
+
<ObserverList
|
|
82
|
+
observers={summaryData.observers.top}
|
|
83
|
+
className={observersListClass}
|
|
84
|
+
generalClasses={generalClasses}
|
|
85
|
+
/>
|
|
86
|
+
)}
|
|
79
87
|
</div>
|
|
80
88
|
</section>
|
|
81
89
|
</div>
|
|
82
90
|
);
|
|
83
|
-
}
|
|
91
|
+
}
|
|
@@ -1,42 +1,63 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { Feature, TaxonPanelFeatures } from "../../__shared";
|
|
3
3
|
import styles from "./tabs.module.scss";
|
|
4
|
-
import {useFeatureTitles} from "../hooks/hooks";
|
|
4
|
+
import { useFeatureTitles } from "../hooks/hooks";
|
|
5
5
|
|
|
6
6
|
export type TabsProps = {
|
|
7
|
-
selectedTab:
|
|
8
|
-
onChangeTab: (tab:
|
|
9
|
-
features:
|
|
7
|
+
selectedTab: Feature;
|
|
8
|
+
onChangeTab: (tab: Feature) => void;
|
|
9
|
+
features: TaxonPanelFeatures;
|
|
10
|
+
className?: string;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
const Tabs = ({ selectedTab, onChangeTab, features }: TabsProps) => {
|
|
13
|
+
const Tabs = ({ selectedTab, onChangeTab, features, className }: TabsProps) => {
|
|
13
14
|
const titles = useFeatureTitles(features);
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
let classes = styles.tabs;
|
|
17
|
+
if (className) {
|
|
18
|
+
classes += ` ${className}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const items = features.map((item) => {
|
|
22
|
+
if (item.feature == Feature.recentObservations) {
|
|
23
|
+
return (
|
|
24
|
+
<li key={Feature.recentObservations}
|
|
25
|
+
onClick={() => onChangeTab(Feature.recentObservations)}
|
|
26
|
+
className={selectedTab === Feature.recentObservations ? styles.selected : ""}>
|
|
27
|
+
{titles[Feature.recentObservations]}
|
|
21
28
|
</li>
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
{
|
|
29
|
+
);
|
|
30
|
+
} else if (item.feature == Feature.commonTaxa) {
|
|
31
|
+
return (
|
|
32
|
+
<li key={Feature.commonTaxa}
|
|
33
|
+
onClick={() => onChangeTab(Feature.commonTaxa)}
|
|
34
|
+
className={selectedTab === Feature.commonTaxa ? styles.selected : ""}>
|
|
35
|
+
{titles[Feature.commonTaxa]}
|
|
27
36
|
</li>
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
{
|
|
37
|
+
);
|
|
38
|
+
} else if (item.feature == Feature.favourites) {
|
|
39
|
+
return (
|
|
40
|
+
<li key={Feature.favourites}
|
|
41
|
+
onClick={() => onChangeTab(Feature.favourites)}
|
|
42
|
+
className={selectedTab === Feature.favourites ? styles.selected : ""}>
|
|
43
|
+
{titles[Feature.favourites]}
|
|
33
44
|
</li>
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
);
|
|
46
|
+
} else if (item.feature == Feature.stats) {
|
|
47
|
+
return (
|
|
48
|
+
<li key={Feature.stats} onClick={() => onChangeTab(Feature.stats)} className={selectedTab === Feature.stats ? styles.selected : ""}>
|
|
49
|
+
{titles[Feature.stats]}
|
|
38
50
|
</li>
|
|
39
|
-
)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// possibly a user has included something wacky
|
|
55
|
+
return undefined;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<ul className={classes}>
|
|
60
|
+
{items}
|
|
40
61
|
</ul>
|
|
41
62
|
);
|
|
42
63
|
}
|