@imerss/inat-components 1.1.2 → 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.
@@ -1,11 +1,11 @@
1
- import {useEffect, useState} from "react";
2
- import {Tab} from "../../__shared";
1
+ import { useEffect, useState } from "react";
2
+ import { Feature, TaxonPanelFeatures } from "../../__shared";
3
3
 
4
4
  const defaultTitles = {
5
- [Tab.recent]: "Recent",
6
- [Tab.commonTaxa]: "Most common",
7
- [Tab.favourites]: "Most favourited",
8
- [Tab.stats]: "General stats"
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
- [Tab.recent]: features.recentObservations?.label || defaultTitles[Tab.recent],
17
- [Tab.commonTaxa]: features.commonTaxa?.label || defaultTitles[Tab.commonTaxa],
18
- [Tab.favourites]: features.favourites?.label || defaultTitles[Tab.favourites],
19
- [Tab.stats]: features.stats?.label || defaultTitles[Tab.stats]
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"; // TODO. Interesting. Why is this required?
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,4 +1,4 @@
1
- import React, {useCallback} from 'react';
1
+ import React, { useCallback } from "react";
2
2
  import styles from "./observation.module.scss";
3
3
 
4
4
  export type ObservationProps = {
@@ -1,21 +1,22 @@
1
- import React, {useEffect, useState} from "react";
1
+ import React from "react";
2
2
  import {
3
3
  formatDate,
4
4
  C,
5
5
  getRecentObservations,
6
6
  RecentObservationData,
7
7
  BaseComponentProps,
8
- DataSource,
9
- BaseClasses
8
+ DataSourceEnum,
9
+ GeneralClasses
10
10
  } from "../../__shared";
11
- import {Observation} from "../observation/observation";
11
+ import { Observation } from "../observation/observation";
12
12
  import Loader from "../loader/loader";
13
- import {NoResults} from "../no-results/no-results";
13
+ import { NoResults } from "../no-results/no-results";
14
+ import { useLoadSourceData } from "../../hooks/useLoadSourceData";
14
15
  import styles from "../shared/css/general.module.scss";
15
16
 
16
17
  export type RecentObservationsProps = BaseComponentProps;
17
18
 
18
- export const RecentObservationLabel = (obs: RecentObservationData & { classes: BaseClasses }) => (
19
+ export const RecentObservationLabel = (obs: RecentObservationData & { classes: GeneralClasses }) => (
19
20
  <div className={styles.obsLabel}>
20
21
  <h3 className={obs.classes?.observationLabelTitle}>{obs.taxonCommonName || obs.taxonName}</h3>
21
22
  <div className={obs.classes?.observationLabelDate}>{formatDate(obs.obsDate)}</div>
@@ -33,59 +34,23 @@ export const RecentObservationLabel = (obs: RecentObservationData & { classes: B
33
34
  export const RecentObservations = ({
34
35
  taxonId,
35
36
  placeId,
36
- data,
37
37
  dataUrl,
38
- source = DataSource.autoLoad,
39
- perPage = C.PER_PAGE,
40
- itemWidth = C.DEFAULT_ITEM_WIDTH,
41
38
  components,
42
39
  className,
43
- classes,
44
- tabDesc
40
+ generalClasses,
41
+ tabDesc,
42
+ source = DataSourceEnum.autoLoad,
43
+ numResults = C.NUM_RESULTS,
44
+ itemWidth = C.DEFAULT_ITEM_WIDTH
45
45
  }: RecentObservationsProps) => {
46
- const [loading, setLoading] = useState(false);
47
- const [observations, setObservations] = useState<any>(() => (source === DataSource.dataProp) ? data : []);
48
-
49
- useEffect(() => {
50
- if (source !== DataSource.autoLoad) {
51
- return;
52
- }
53
- if (!taxonId) {
54
- console.error("Please supply a `taxonId` prop for the `autoLoad` source prop option.");
55
- return;
56
- }
57
- if (!placeId) {
58
- console.error("Please supply a `placeId` prop for the `autoLoad` source prop option.");
59
- return;
60
- }
61
-
62
- (async () => {
63
- setLoading(true);
64
- const obs = await getRecentObservations({taxonId, placeId, perPage});
65
- setObservations(obs.results);
66
- setLoading(false);
67
- })();
68
- }, [source, taxonId, placeId, perPage]);
69
-
70
- useEffect(() => {
71
- if (source !== DataSource.url) {
72
- return;
73
- }
74
-
75
- if (!dataUrl) {
76
- console.error("Please supply a `dataUrl` prop for the `url` source prop option.");
77
- return;
78
- }
79
-
80
- (async () => {
81
- setLoading(true);
82
- const obs = await fetch(dataUrl);
83
- const json = await obs.json();
84
- setObservations(json.results);
85
- setLoading(false);
86
- })();
87
- }, [source, dataUrl]);
88
-
46
+ const { loading, results: observations } = useLoadSourceData({
47
+ taxonId,
48
+ placeId,
49
+ numResults,
50
+ dataUrl,
51
+ source,
52
+ action: getRecentObservations
53
+ });
89
54
  const Load = components?.loader ? components.loader as any : Loader;
90
55
  const Label = components?.label ? components.label as any : RecentObservationLabel;
91
56
 
@@ -94,9 +59,10 @@ export const RecentObservations = ({
94
59
  componentClasses += ` ${className}`;
95
60
  }
96
61
  let descClasses = styles.tabDesc;
97
- if (classes?.tabDesc) {
98
- descClasses += ` ${classes.tabDesc}`;
62
+ if (generalClasses?.tabDesc) {
63
+ descClasses += ` ${generalClasses.tabDesc}`;
99
64
  }
65
+
100
66
  return (
101
67
  <div className={componentClasses}>
102
68
  {tabDesc && <p className={descClasses}>{tabDesc}</p>}
@@ -109,7 +75,7 @@ export const RecentObservations = ({
109
75
  imageUrl={obs.imageUrl.replace(/square/, "medium")}
110
76
  itemWidth={itemWidth}
111
77
  linkUrl={obs.obsUrl}>
112
- <Label {...obs} classes={classes} />
78
+ <Label {...obs} classes={generalClasses} />
113
79
  </Observation>
114
80
  ))}
115
81
  </div>
@@ -1,8 +1,8 @@
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, className = ''}: { observers: Observer[], className?: string }) => {
5
+ export const ObserverList = ({ observers, generalClasses, className = '' }: { observers: Observer[], generalClasses?: GeneralClasses, className?: string }) => {
6
6
  let classes = styles.top;
7
7
  if (className) {
8
8
  classes += ` ${className}`;
@@ -15,7 +15,7 @@ export const ObserverList = ({observers, className = ''}: { observers: Observer[
15
15
  {obs.iconUrl ? <img src={obs.iconUrl || ""} className={styles.avatar} alt="User icon"/> :
16
16
  <div className={styles.noAvatar}/>}
17
17
  <h3>{obs.userName}</h3>
18
- <label>{numberWithCommas(obs.numObservations)}</label>
18
+ <label className={generalClasses?.observationCount}>{numberWithCommas(obs.numObservations)}</label>
19
19
  </a>
20
20
  </li>
21
21
  ))}
@@ -1,76 +1,69 @@
1
- import React, {useEffect, useState} from "react";
2
- import {BaseClasses, numberWithCommas, DataSource, getSummary, FavouritesRespData} from "../../__shared";
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: string | number;
13
- data?: FavouritesRespData;
12
+ year: number;
13
+ source: DataSource;
14
14
  dataUrl?: string;
15
- classes?: BaseClasses;
15
+ generalClasses?: GeneralClasses;
16
+ className?: string;
16
17
  tabDesc?: string;
18
+ observersListClass?: string;
19
+ statsCountSummaryClass?: string;
17
20
  };
18
21
 
19
- export const Summary = ({ source, data, dataUrl, taxonId, placeId, year, classes, tabDesc }: SummaryProps) => {
20
- const [summaryData, setSummaryData] = useState<any>({});
21
- const [loading, setLoading] = useState(false);
22
-
23
- useEffect(() => {
24
- if (source !== DataSource.autoLoad) {
25
- return;
26
- }
27
- if (!taxonId) {
28
- console.error("Please supply a `taxonId` prop for the `autoLoad` source prop option.");
29
- return;
30
- }
31
- if (!placeId) {
32
- console.error("Please supply a `placeId` prop for the `autoLoad` source prop option.");
33
- return;
34
- }
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,
35
41
 
36
- (async () => {
37
- setLoading(true);
38
- const data = await getSummary({ taxonId, placeId, year });
39
- setSummaryData(data);
40
- setLoading(false);
41
- })();
42
- }, [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);
43
46
 
44
- useEffect(() => {
45
- if (source !== DataSource.url) {
46
- return;
47
+ return { results };
47
48
  }
48
-
49
- if (!dataUrl) {
50
- console.error("Please supply a `dataUrl` prop for the `url` source prop option.");
51
- return;
52
- }
53
-
54
- (async () => {
55
- setLoading(true);
56
- const obs = await fetch(dataUrl);
57
- const json = await obs.json();
58
- setSummaryData(json);
59
- setLoading(false);
60
- })();
61
- }, [source, dataUrl]);
49
+ });
62
50
 
63
51
  let countSummaryClasses = styles.countSummary;
64
- if (classes?.statsCountSummary) {
65
- countSummaryClasses += ` ${classes.statsCountSummary}`;
52
+ if (statsCountSummaryClass) {
53
+ countSummaryClasses += ` ${statsCountSummaryClass}`;
66
54
  }
67
55
  let descClasses = styles.tabDesc;
68
- if (classes?.tabDesc) {
69
- descClasses += ` ${classes.tabDesc}`;
56
+ if (generalClasses?.tabDesc) {
57
+ descClasses += ` ${generalClasses.tabDesc}`;
58
+ }
59
+
60
+ let classList = styles.summaryPage;
61
+ if (className) {
62
+ classList += ` ${className}`;
70
63
  }
71
64
 
72
65
  return (
73
- <div className={styles.summaryPage}>
66
+ <div className={classList}>
74
67
  {tabDesc && <p className={descClasses}>{tabDesc}</p>}
75
68
  <Loader loading={loading} />
76
69
  <ul className={countSummaryClasses}>
@@ -84,9 +77,15 @@ export const Summary = ({ source, data, dataUrl, taxonId, placeId, year, classes
84
77
  </div>
85
78
  <div className={styles.observersBlock}>
86
79
  <h1>Top observers</h1>
87
- {summaryData.observers?.top && <ObserverList observers={summaryData.observers.top} className={classes?.observersList} />}
80
+ {summaryData.observers?.top && (
81
+ <ObserverList
82
+ observers={summaryData.observers.top}
83
+ className={observersListClass}
84
+ generalClasses={generalClasses}
85
+ />
86
+ )}
88
87
  </div>
89
88
  </section>
90
89
  </div>
91
90
  );
92
- }
91
+ }
@@ -1,12 +1,12 @@
1
1
  import React from "react";
2
- import {Tab} from "../../__shared";
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: Tab;
8
- onChangeTab: (tab: Tab) => void;
9
- features: any;
7
+ selectedTab: Feature;
8
+ onChangeTab: (tab: Feature) => void;
9
+ features: TaxonPanelFeatures;
10
10
  className?: string;
11
11
  }
12
12
 
@@ -18,31 +18,46 @@ const Tabs = ({ selectedTab, onChangeTab, features, className }: TabsProps) => {
18
18
  classes += ` ${className}`;
19
19
  }
20
20
 
21
- return (
22
- <ul className={classes}>
23
- {features.recentObservations && (
24
- <li onClick={() => onChangeTab(Tab.recent)}
25
- className={selectedTab === Tab.recent ? styles.selected : ""}>
26
- {titles[Tab.recent]}
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]}
27
28
  </li>
28
- )}
29
- {features.commonTaxa && (
30
- <li onClick={() => onChangeTab(Tab.commonTaxa)}
31
- className={selectedTab === Tab.commonTaxa ? styles.selected : ""}>
32
- {titles[Tab.commonTaxa]}
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]}
33
36
  </li>
34
- )}
35
- {features.favourites && (
36
- <li onClick={() => onChangeTab(Tab.favourites)}
37
- className={selectedTab === Tab.favourites ? styles.selected : ""}>
38
- {titles[Tab.favourites]}
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]}
39
44
  </li>
40
- )}
41
- {features.stats && (
42
- <li onClick={() => onChangeTab(Tab.stats)} className={selectedTab === Tab.stats ? styles.selected : ""}>
43
- {titles[Tab.stats]}
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]}
44
50
  </li>
45
- )}
51
+ );
52
+ }
53
+
54
+ // possibly a user has included something wacky
55
+ return undefined;
56
+ });
57
+
58
+ return (
59
+ <ul className={classes}>
60
+ {items}
46
61
  </ul>
47
62
  );
48
63
  }
@@ -1,127 +1,106 @@
1
- import React, {useState} from "react";
2
- import {getSourceFile, Tab, DataSource, Feature, ConfigFile, BaseClasses, TabDescs} 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";
1
+ import React, { useMemo, useState } from "react";
2
+ import { C, getSourceFile, DataSource, DataSourceEnum, Feature, GeneralClasses, TaxonPanelFeatures } from "../../__shared";
3
+ import { RecentObservations } 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
7
  import Years from "../year-dropdown/year-dropdown";
8
8
  import Tabs from "../tabs/tabs";
9
- import {useFeatureTitles} from "../hooks/hooks";
10
- import styles from '../shared/css/general.module.scss';
9
+ import { convertFeaturesToObj, useFeatureTitles } from "../hooks/hooks";
10
+ import styles from "../shared/css/general.module.scss";
11
11
 
12
12
  export type TaxonPanelProps = {
13
- taxonId: number;
14
- placeId: number;
13
+ taxon: {
14
+ id: number;
15
+ str: string;
16
+ };
17
+ place: {
18
+ id: number;
19
+ str: string;
20
+ }
21
+ dataSourceBaseUrl: string;
15
22
  dataSource: DataSource;
16
- config: ConfigFile;
17
- baseUrl: string;
23
+ features: TaxonPanelFeatures;
24
+ generalClasses: GeneralClasses;
18
25
  itemWidth?: number;
19
- classes?: BaseClasses;
20
- tabDescs?: TabDescs;
21
26
  }
22
27
 
23
- const TaxonPanel = ({ taxonId, placeId, dataSource, config, baseUrl, itemWidth, classes, tabDescs }: TaxonPanelProps) => {
24
- const [tab, setTab] = useState(Tab.recent);
28
+ const TaxonPanel = ({ taxon, place, features, dataSource, dataSourceBaseUrl, itemWidth, generalClasses }: TaxonPanelProps) => {
29
+ const [tab, setTab] = useState(features[0].feature);
25
30
  const [year, setYear] = useState<string>("all");
26
- const titles = useFeatureTitles(config.features);
27
- const [taxonInfo] = useState(() => config.taxa.find((i) => i.taxonId === taxonId));
28
- const [placeInfo] = useState(() => config.places.find((i) => i.placeId === placeId));
31
+ const featuresObj = useMemo(() => {
32
+ return convertFeaturesToObj(features);
33
+ }, [features]);
34
+ const titles = useFeatureTitles(featuresObj);
35
+
36
+ const getProps = (feature: Feature): any => {
37
+ const props: any = {
38
+ source: dataSource,
39
+ itemWidth,
40
+ tabDesc: featuresObj[feature]?.desc,
41
+ generalClasses,
42
+ className: featuresObj[feature]?.className
43
+ };
44
+ if (dataSource === DataSourceEnum.url) {
45
+ props.dataUrl = dataSourceBaseUrl + '/' + getSourceFile(feature, taxon, place, year);
46
+ } else {
47
+ props.placeId = place.id;
48
+ props.taxonId = taxon.id;
49
+ }
50
+
51
+ if (feature !== Feature.stats) {
52
+ props.numResults = featuresObj[feature]?.numResults;
53
+ }
54
+
55
+ if (feature !== Feature.recentObservations) {
56
+ props.year = year;
57
+ }
58
+
59
+ return props;
60
+ };
29
61
 
30
- console.log("..." , classes);
31
62
  const getCurrentTab = () => {
32
63
  switch (tab) {
33
- case Tab.recent: {
34
- const props: Partial<RecentObservationsProps> = {
35
- source: dataSource,
36
- itemWidth,
37
- classes,
38
- tabDesc: tabDescs?.recentDesc
39
- };
40
- if (dataSource === DataSource.url) {
41
- props.dataUrl = baseUrl + '/' + getSourceFile(Feature.recentObservations, taxonInfo, placeInfo);
42
- } else {
43
- props.placeId = placeId;
44
- props.taxonId = taxonId;
45
- }
46
- return (
47
- <RecentObservations {...props} />
48
- );
49
- }
64
+ case Feature.recentObservations:
65
+ return <RecentObservations {...getProps(Feature.recentObservations)} />;
66
+ case Feature.commonTaxa:
67
+ return <CommonTaxa {...getProps(Feature.commonTaxa)} />;
68
+ case Feature.favourites:
69
+ return <Favourites {...getProps(Feature.favourites)} />;
50
70
 
51
- case Tab.commonTaxa: {
52
- const props: any = {
53
- classes,
54
- source: dataSource,
55
- itemWidth,
56
- year,
57
- tabDesc: tabDescs?.mostCommonDesc
58
- };
59
- if (dataSource === DataSource.url) {
60
- props.dataUrl = baseUrl + '/' + getSourceFile(Feature.commonTaxa, taxonInfo, placeInfo, year);
61
- } else {
62
- props.placeId = placeId;
63
- props.taxonId = taxonId;
64
- }
71
+ case Feature.stats:
65
72
  return (
66
- <CommonTaxa {...props} />
73
+ <Summary
74
+ {...getProps(Feature.stats)}
75
+ observersListClass={featuresObj[Feature.stats]?.observersListClass}
76
+ statsCountSummaryClass={featuresObj[Feature.stats]?.statsCountSummaryClass}
77
+ />
67
78
  );
68
- }
69
-
70
- case Tab.favourites: {
71
- const props: any = {
72
- classes,
73
- itemWidth,
74
- source: dataSource,
75
- year,
76
- tabDesc: tabDescs?.mostFavouritedDesc
77
- };
78
- if (dataSource === DataSource.url) {
79
- props.dataUrl = baseUrl + '/' + getSourceFile(Feature.favourites, taxonInfo, placeInfo, year);
80
- } else {
81
- props.placeId = placeId;
82
- props.taxonId = taxonId;
83
- }
84
- return (
85
- <Favourites {...props} />
86
- );
87
- }
88
-
89
- case Tab.stats: {
90
- const props: any = {
91
- classes,
92
- source: dataSource,
93
- year,
94
- tabDesc: tabDescs?.generalStatsDesc
95
- };
96
- if (dataSource === DataSource.url) {
97
- props.dataUrl = baseUrl + '/' + getSourceFile(Feature.stats, taxonInfo, placeInfo, year);
98
- } else {
99
- props.placeId = placeId;
100
- props.taxonId = taxonId;
101
- }
102
- return <Summary {...props} />;
103
- }
104
79
  }
105
80
  };
106
81
 
107
- const tabsClass = classes?.tabs || '';
108
- const yearDropdownClass = classes?.yearsDropdown || '';
82
+ const tabsClass = generalClasses?.tabsElement || '';
83
+ const yearDropdownClass = generalClasses?.yearsDropdown || '';
109
84
 
110
85
  return (
111
86
  <div className={styles.page}>
112
- <Tabs
87
+ {features.length > 1 && <Tabs
113
88
  selectedTab={tab}
114
89
  onChangeTab={setTab}
115
- features={config.features}
90
+ features={features}
116
91
  className={tabsClass}
117
- />
92
+ />}
118
93
  <div>
119
- {tab !== Tab.recent && (
94
+ {tab !== Feature.recentObservations && (
120
95
  <div style={{ float: "right" }} className={yearDropdownClass}>
121
- <Years value={year} onChange={setYear} />
96
+ <Years
97
+ value={year}
98
+ numYears={featuresObj[tab]?.numYears || C.DEFAULT_NUM_YEARS}
99
+ onChange={setYear}
100
+ />
122
101
  </div>
123
102
  )}
124
- <h1 className={classes?.pageHeadings}>{titles[tab]}</h1>
103
+ <h1 className={generalClasses?.pageHeadings}>{titles[tab]}</h1>
125
104
  </div>
126
105
  {getCurrentTab()}
127
106
  </div>