+ {shouldShowMapTabs && (
+
+ )}
+ {shouldShowTimeTravel && (
+
+
+
+ )}
diff --git a/src/features/projectsV2/ProjectsMap/microComponents/FeatureFlag.tsx b/src/features/projectsV2/ProjectsMap/microComponents/FeatureFlag.tsx
new file mode 100644
index 0000000000..279b09af16
--- /dev/null
+++ b/src/features/projectsV2/ProjectsMap/microComponents/FeatureFlag.tsx
@@ -0,0 +1,13 @@
+import type { ReactElement, ReactNode } from 'react';
+
+interface Props {
+ condition: boolean;
+ children?: ReactNode;
+}
+
+export default function FeatureFlag({
+ condition,
+ children,
+}: Props): ReactElement {
+ return <>{condition ? <>{children}> : null}>;
+}
diff --git a/src/features/projectsV2/ProjectsMap/microComponents/FireLocations.tsx b/src/features/projectsV2/ProjectsMap/microComponents/FireLocations.tsx
new file mode 100644
index 0000000000..972137c970
--- /dev/null
+++ b/src/features/projectsV2/ProjectsMap/microComponents/FireLocations.tsx
@@ -0,0 +1,71 @@
+import type { ReactElement } from 'react';
+import type {
+ FireFeature,
+ FireFeatureCollection,
+} from '../../../common/types/fireLocation';
+
+import { APIError } from '@planet-sdk/common';
+import { useRouter } from 'next/router';
+import { useEffect, useState } from 'react';
+import { Marker, Source } from 'react-map-gl-v7/maplibre';
+import { getRequest } from '../../../../utils/apiRequests/api';
+import FirePopup from '../FirePopup';
+
+const ALERT_DURATION = '30d';
+
+export default function FireLocations(): ReactElement {
+ const { query } = useRouter();
+
+ const { site } = query;
+
+ const [fireFeatures, setFireFeatures] = useState
([]);
+
+ useEffect(() => {
+ if (!site) return;
+
+ const fetchFires = async () => {
+ try {
+ const searchParams = new URLSearchParams();
+ searchParams.append('remoteId', site as string);
+ searchParams.append('span', ALERT_DURATION);
+ const fireAlertApiUrl =
+ process.env.NEXT_PUBLIC_FIREALERT_ENDPOINT ??
+ 'https://fa.pp.eco/api/v1';
+ const url = `${fireAlertApiUrl}/fires?${searchParams.toString()}`;
+ const fetchedFires = await getRequest({ url });
+ if (
+ fetchedFires?.type === 'FeatureCollection' &&
+ fetchedFires?.features?.length > 0
+ ) {
+ setFireFeatures([...fetchedFires.features]);
+ }
+ } catch (error) {
+ if (error instanceof APIError) {
+ console.log(error.errors?.message ?? error.message);
+ }
+ }
+ };
+ fetchFires();
+ }, [site]);
+
+ return (
+ <>
+
+ {fireFeatures?.map((f) => (
+
+
+
+ ))}
+
+ >
+ );
+}
diff --git a/src/features/projectsV2/ProjectsMap/microComponents/PlantLocations.tsx b/src/features/projectsV2/ProjectsMap/microComponents/PlantLocations.tsx
index c48016d23f..58ffc03577 100644
--- a/src/features/projectsV2/ProjectsMap/microComponents/PlantLocations.tsx
+++ b/src/features/projectsV2/ProjectsMap/microComponents/PlantLocations.tsx
@@ -220,6 +220,7 @@ export default function PlantLocations(): React.ReactElement {
/>
{selectedPlantLocation &&
selectedPlantLocation.type !== 'single-tree-registration' &&
+ (selectedInterventionType === 'multi-tree-registration' || selectedInterventionType === 'enrichment-planting' || selectedInterventionType ==='all' || selectedInterventionType === 'default') &&
viewState.zoom > 14 &&
selectedPlantLocation.sampleInterventions
? selectedPlantLocation.sampleInterventions.map((spl) => {
diff --git a/src/temp/stories/projectDetails/SingleMapTab.stories.tsx b/src/features/projectsV2/ProjectsMap/stories/SingleMapTab.stories.tsx
similarity index 80%
rename from src/temp/stories/projectDetails/SingleMapTab.stories.tsx
rename to src/features/projectsV2/ProjectsMap/stories/SingleMapTab.stories.tsx
index 6a5d7185a0..5c011609fe 100644
--- a/src/temp/stories/projectDetails/SingleMapTab.stories.tsx
+++ b/src/features/projectsV2/ProjectsMap/stories/SingleMapTab.stories.tsx
@@ -1,6 +1,6 @@
-import SatelliteIcon from '../../../../public/assets/images/icons/SatelliteIcon';
+import SatelliteIcon from '../../../../../public/assets/images/icons/SatelliteIcon';
import type { Meta, StoryObj } from '@storybook/react';
-import SingleTab from '../../ProjectMapTabs/SingleTab';
+import SingleTab from '../ProjectMapTabs/SingleTab';
const meta: Meta = {
title: 'Projects/Details/SingleTab',
diff --git a/src/temp/stories/projectDetails/Tabs.stories.tsx b/src/features/projectsV2/ProjectsMap/stories/Tabs.stories.tsx
similarity index 71%
rename from src/temp/stories/projectDetails/Tabs.stories.tsx
rename to src/features/projectsV2/ProjectsMap/stories/Tabs.stories.tsx
index 3ba569c65b..d64dcaaeb3 100644
--- a/src/temp/stories/projectDetails/Tabs.stories.tsx
+++ b/src/features/projectsV2/ProjectsMap/stories/Tabs.stories.tsx
@@ -1,5 +1,5 @@
import type { Meta, StoryObj } from '@storybook/react';
-import Tabs from '../../ProjectMapTabs/Tabs';
+import Tabs from '../ProjectMapTabs';
const meta: Meta = {
title: 'Projects/Details/Tabs',
@@ -11,6 +11,6 @@ type Story = StoryObj;
export const TabsView: Story = {
args: {
- selected: 'satellite', //initially selected option
+ selectedTab: 'field', //initially selected option
},
};
diff --git a/src/temp/stories/projectDetails/TimeTravelDropdown.stories.tsx b/src/features/projectsV2/ProjectsMap/stories/TimeTravelDropdown.stories.tsx
similarity index 52%
rename from src/temp/stories/projectDetails/TimeTravelDropdown.stories.tsx
rename to src/features/projectsV2/ProjectsMap/stories/TimeTravelDropdown.stories.tsx
index 104542c972..2d9a53a186 100644
--- a/src/temp/stories/projectDetails/TimeTravelDropdown.stories.tsx
+++ b/src/features/projectsV2/ProjectsMap/stories/TimeTravelDropdown.stories.tsx
@@ -1,33 +1,36 @@
import TimeTravelDropdown from '../../TimeTravelDropdown';
-import sources from '../../../../public/data/maps/sources.json';
import type { Meta, StoryObj } from '@storybook/react';
const meta: Meta = {
title: 'Projects/Details/TimeTravelDropdown',
component: TimeTravelDropdown,
+ argTypes: {
+ onYearChange: { action: 'year changed' },
+ onSourceChange: { action: 'source changed' },
+ },
};
export default meta;
type Story = StoryObj;
-const yearList = ['2024', '2023', '2022', '2021', '2020', '2019', '2018'];
+const availableYears = ['2024', '2023', '2022', '2021', '2020', '2019', '2018'];
export const Open: Story = {
args: {
- labelYear: '2018',
- labelSource: 'Esri',
- yearList: yearList,
- sourceList: Object.values(sources),
+ defaultYear: '2018',
+ defaultSource: 'esri',
+ availableYears,
+ availableSources: ['esri'],
isOpen: true,
},
};
export const Close: Story = {
args: {
- labelYear: '2018',
- labelSource: 'Esri',
- yearList: yearList,
- sourceList: Object.values(sources),
+ defaultYear: '2021',
+ defaultSource: 'esri',
+ availableYears,
+ availableSources: ['esri'],
isOpen: false,
},
};
diff --git a/src/features/projectsV2/ProjectsMapContext.tsx b/src/features/projectsV2/ProjectsMapContext.tsx
index 70785df1c7..92a955012e 100644
--- a/src/features/projectsV2/ProjectsMapContext.tsx
+++ b/src/features/projectsV2/ProjectsMapContext.tsx
@@ -2,6 +2,7 @@ import type { FC } from 'react';
import type { ViewState } from 'react-map-gl-v7';
import type { MapStyle } from 'react-map-gl-v7/maplibre';
import type { SetState } from '../common/types/common';
+import type { ProjectTimeTravelConfig } from '../../utils/mapsV2/timeTravel';
import { useContext, useMemo, createContext, useState, useEffect } from 'react';
import getMapStyle from '../../utils/maps/getMapStyle';
@@ -63,6 +64,8 @@ interface ProjectsMapState {
* Updates the state of a single map-related option.
*/
updateMapOption: (option: keyof MapOptions, value: boolean) => void;
+ timeTravelConfig: ProjectTimeTravelConfig | null;
+ setTimeTravelConfig: SetState;
}
const ProjectsMapContext = createContext(null);
@@ -73,6 +76,8 @@ export const ProjectsMapProvider: FC = ({ children }) => {
const [mapOptions, setMapOptions] = useState({
showProjects: true,
});
+ const [timeTravelConfig, setTimeTravelConfig] =
+ useState(null);
const handleViewStateChange = (newViewState: Partial) => {
setViewState((prev) => ({
@@ -109,8 +114,10 @@ export const ProjectsMapProvider: FC = ({ children }) => {
setIsSatelliteView,
mapOptions,
updateMapOption,
+ timeTravelConfig,
+ setTimeTravelConfig,
}),
- [mapState, viewState, mapOptions, isSatelliteView]
+ [mapState, viewState, mapOptions, isSatelliteView, timeTravelConfig]
);
return (
diff --git a/src/features/projectsV2/TimeTravelDropdown/TimeTravelDropdown.module.scss b/src/features/projectsV2/TimeTravelDropdown/TimeTravelDropdown.module.scss
new file mode 100644
index 0000000000..62b48c10c0
--- /dev/null
+++ b/src/features/projectsV2/TimeTravelDropdown/TimeTravelDropdown.module.scss
@@ -0,0 +1,76 @@
+@import '../../../theme/theme';
+
+.menuContainer {
+ font-size: $fontXSmall;
+}
+
+.menuButton {
+ border-radius: 8px;
+ width: 150px;
+ background: $light;
+ height: 36px;
+ display: flex;
+ align-items: center;
+ padding: 0 10px;
+ justify-content: space-between;
+ font-size: $fontXXSmall;
+ gap: 4px;
+ cursor: pointer;
+ .menuButtonText {
+ margin: 0 0 0 6px;
+ }
+ .highlighted {
+ font-weight: 700;
+ }
+}
+
+.menuButtonTitle {
+ display: flex;
+ align-items: center;
+}
+
+.menuItems {
+ display: flex;
+ width: 150px;
+ margin-top: 16px;
+ border-radius: 8px;
+ color: $grayFontColorNew;
+}
+
+.yearMenuContainer {
+ border-radius: 8px 0 0 8px;
+ background-color: $lightGrayBackgroundColorNew;
+ padding: 16px;
+ display: flex;
+ flex-direction: column;
+ .unselectedMenuItem,
+ .selectedMenuItem {
+ opacity: 1;
+ margin-top: 6px;
+ cursor: pointer;
+ }
+ .selectedMenuItem {
+ color: $grayFontColorNew;
+ }
+}
+
+.sourceMenuContainer {
+ background: $light;
+ padding: 16px;
+ border-radius: 0px 8px 8px 0;
+ flex: 1;
+ .unselectedMenuItem,
+ .selectedMenuItem {
+ list-style: none;
+ margin-top: 6px;
+ cursor: pointer;
+ }
+ .selectedMenuItem {
+ color: #333; // to be replaced
+ }
+}
+
+.selectedMenuItem {
+ text-shadow: 0 0 0.01px;
+ font-weight: 700;
+}
diff --git a/src/features/projectsV2/TimeTravelDropdown/index.tsx b/src/features/projectsV2/TimeTravelDropdown/index.tsx
new file mode 100644
index 0000000000..384dfcb757
--- /dev/null
+++ b/src/features/projectsV2/TimeTravelDropdown/index.tsx
@@ -0,0 +1,121 @@
+import type { SourceName } from '../../../utils/mapsV2/timeTravel';
+
+import React, { useState } from 'react';
+import { useTranslations } from 'next-intl';
+import styles from './TimeTravelDropdown.module.scss';
+import CalendarIcon from '../../../../public/assets/images/icons/projectV2/CalendarIcon';
+import DropdownUpArrow from '../../../../public/assets/images/icons/projectV2/DropdownUpArrow';
+import DropdownDownArrow from '../../../../public/assets/images/icons/projectV2/DropdownDownArrow';
+
+const SOURCE_LABELS: Record = {
+ esri: 'Esri',
+};
+
+interface TimeTravelDropdownProps {
+ defaultYear: string;
+ defaultSource: SourceName;
+ availableYears: string[];
+ availableSources: SourceName[];
+ isOpen: boolean;
+ onYearChange: (year: string) => void;
+ onSourceChange: (source: SourceName) => void;
+ customClassName?: string;
+}
+
+const TimeTravelDropdown = ({
+ defaultYear,
+ defaultSource,
+ availableYears,
+ availableSources,
+ isOpen,
+ onYearChange,
+ onSourceChange,
+ customClassName,
+}: TimeTravelDropdownProps) => {
+ const tTimeTravel = useTranslations('ProjectDetails.timeTravel');
+
+ const [selectedYear, setSelectedYear] = useState(defaultYear);
+ const [selectedSource, setSelectedSource] = useState(defaultSource);
+ const [isMenuOpen, setIsMenuOpen] = useState(isOpen);
+
+ const handleChangeYear = (year: string) => {
+ setSelectedYear(year);
+ onYearChange(year);
+ };
+
+ const handleChangeSource = (source: SourceName) => {
+ setSelectedSource(source);
+ onSourceChange(source);
+ };
+
+ const isOptionSelected = (option: string, selectedValue: string): boolean =>
+ option.toLowerCase() === selectedValue.toLowerCase();
+
+ return (
+
+
+ {isMenuOpen && (
+
+
+ {availableYears?.map((year, index) => (
+
+ ))}
+
+
+ {availableSources?.map((source, index) => (
+ - handleChangeSource(source)}
+ className={`${
+ isOptionSelected(source, selectedSource)
+ ? styles.selectedMenuItem
+ : styles.unselectedMenuItem
+ }`}
+ >
+ {SOURCE_LABELS[source]}
+
+ ))}
+
+
+ )}
+
+ );
+};
+
+export default TimeTravelDropdown;
diff --git a/src/temp/FirePopup/FirePopup.module.scss b/src/temp/FirePopup/FirePopup.module.scss
deleted file mode 100644
index e1f2102453..0000000000
--- a/src/temp/FirePopup/FirePopup.module.scss
+++ /dev/null
@@ -1,98 +0,0 @@
-@import '../../theme/theme';
-
-.arrow {
- display: block;
- height: 0px;
- width: 0px;
- border: 10px solid transparent;
- border-top-color: $light;
- position: absolute;
- bottom: -19px;
- left: calc(50% - 10px);
-}
-
-//fireIcon style : to be removed
-.fireIcon {
- width:100%;
- display: flex;
- justify-content: center;
- margin-top: 20%;
- padding-top:20px;
- cursor: pointer;
-}
-
-.popupContainer {
- background-color: $backgroundColor;
- border-radius: 8px;
- svg {
- overflow: visible;
- }
-}
-
-.popupTitle {
- display: flex;
- justify-content: space-between;
- background: rgba(232, 111, 86, 0.2); //to be replaced
- align-items: center;
- padding: 10px 14px;
- border-radius: 8px 8px 0 0;
-
- .titleText {
- display: flex;
- align-items: center;
- gap: 7px;
- font-weight: 700;
- color: #333; //to be replaced
- font-size: $fontXSmall;
- }
-}
-
-.popupText {
- padding: 14px;
- font-size: $fontXSmall;
- font-weight: 400;
- color: #4F4F4F; //to be replaced
- display: flex;
- flex-direction: column;
- gap: 4px;
- .coordinates {
- color: #828282;//to be replaced
- }
- span {
- font-weight: 700;
- }
-
-}
-
-.setUpAlertsContainer {
- display: flex;
- justify-content: space-between;
- gap: 13px;
- .setUpAlerts {
- span {
- color: $dangerColorNew;
- }
- }
-}
-
-.infoIconPopupContainer {
- padding: 10px;
- max-width: 123px;
- font-size: $fontXXSmall;
- border-radius: 8px;
-}
-
-.timeDuration {
- font-size: $fontXXSmall;
- font-weight: 600;
- color: $dangerColorNew;
- display: flex;
- gap: 3px;
-}
-
-//to be removed
-@include xsPhoneView {
- .fireIcon {
- margin-top: 60%;
- }
-}
\ No newline at end of file
diff --git a/src/temp/FirePopup/index.tsx b/src/temp/FirePopup/index.tsx
deleted file mode 100644
index 20b3c4bef2..0000000000
--- a/src/temp/FirePopup/index.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import { Popper } from '@mui/material';
-import React from 'react';
-import styles from './FirePopup.module.scss';
-import FireIcon from '../icons/FireIcon';
-import FirePopupIcon from '../icons/FirePopupIcon';
-import InfoIconPopup from '../../features/projectsV2/ProjectDetails/components/microComponents/InfoIconPopup';
-import RightArrowIcon from '../../../public/assets/images/icons/projectV2/RightArrowIcon';
-import { useTranslations } from 'next-intl';
-
-interface Props {
- isOpen: boolean;
-}
-
-// Currently contains hardcoded data, component would need refactoring based on the api/data when available.
-
-export default function FirePopup({ isOpen }: Props) {
- const anchorRef = React.useRef(null);
- const [arrowRef, setArrowRef] = React.useState(null);
- const [showPopup, setShowPopup] = React.useState(isOpen);
- const tProjectDetails = useTranslations('ProjectDetails');
-
- return (
- <>
-
- setShowPopup(true)}
- onMouseLeave={() => setShowPopup(false)}
- className={styles.fireIcon}
- >
-
-
- >
- );
-}
diff --git a/src/temp/ProjectMapTabs/ProjectMapTabs.module.scss b/src/temp/ProjectMapTabs/ProjectMapTabs.module.scss
deleted file mode 100644
index 6d9831e554..0000000000
--- a/src/temp/ProjectMapTabs/ProjectMapTabs.module.scss
+++ /dev/null
@@ -1,111 +0,0 @@
-@import '../../theme/theme';
-.tabsContainer {
-display: flex;
-width: fit-content;
-height: 48px;
-border-radius: 12px;
-background: $backgroundColor;
-align-items: center;
-justify-content: center;
-padding: 0px 5px;
-gap: 16px;
-position: fixed;
-left: 10px;
-right: auto;
-margin: 0 auto;
-top: 100px;;
-z-index: 10;
-color: $dark;
-}
-.singleTabOption {
- display: flex;
- flex-direction: row;
- justify-content: center;
- align-items: center;
- font-weight: 700;
- cursor: pointer;
- width: 50px;
- height: 40px;
- border-radius: 8px;
- gap:6px;
- }
-
- .singleTabOption svg {
- height: 18px;
- }
- .singleTabOption p {
- display: none;
- }
-
- .selected {
- width: 66px;
- color: $backgroundColor;
- background-color: $primaryColorNew;
- }
-
- .showSeparator1, .showSeparator2 {
- background: $dark;
- width: 0.5px;
- height: 30px;
- position: absolute;
- top: 20%;
- opacity: 0.3;
- }
-
- .showSeparator1{
- left: 63px;
- }
-
- .showSeparator2 {
- left: 144px;
- }
-
- .hideSeparator {
- display: none;
- }
-
- @include smTabletView {
- .tabsContainer {
- top: 100px;
- }
-
- }
-
- @include mdTabletView {
- .tabsContainer {
- left: 400px;
- top: 110px;
- }
-
- }
- @include xlDesktopView {
- .tabsContainer{
- left: 400px;
- top: 110px;
- }
-
- }
-
- @include lgLaptopView {
- .tabsContainer {
- right: auto;
- }
- .singleTabOption {
- width: 150px;
- p {
- display: block;
- font-size: $fontXSmall;
- font-weight: 700;
- }
- }
- .selected {
- width: 166px;
- }
- .showSeparator1{
- left: 164px;
- }
-
- .showSeparator2 {
- left: 345px;
- }
- }
diff --git a/src/temp/ProjectMapTabs/Tabs.tsx b/src/temp/ProjectMapTabs/Tabs.tsx
deleted file mode 100644
index 7d10699820..0000000000
--- a/src/temp/ProjectMapTabs/Tabs.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import React, { useState } from 'react';
-import styles from './ProjectMapTabs.module.scss';
-import SingleTab from './SingleTab';
-import SatelliteAnalysisIcon from '../icons/SatelliteAnalysisIcon';
-import { useTranslations } from 'next-intl';
-import FieldDataIcon from '../icons/FieldDataIcon';
-import SatelliteIcon from '../../../public/assets/images/icons/SatelliteIcon';
-
-type SelectedMode = 'satellite' | 'field' | 'timeTravel';
-
-interface TabsProps {
- selected: SelectedMode;
-}
-
-const Tabs = ({ selected }: TabsProps) => {
- const [selectedMode, setSelectedMode] = useState(selected);
-
- const allTabsList: SelectedMode[] = ['satellite', 'field', 'timeTravel'];
- const setSeparatorVisibility = (
- selectedMode: SelectedMode,
- separatorId: number
- ) => {
- const index = allTabsList.indexOf(selectedMode);
- if (separatorId !== index - 1 && separatorId !== index) return true;
- return false;
- };
-
- const tProjectDetails = useTranslations('ProjectDetails');
- const tMaps = useTranslations('Maps');
-
- return (
-
-
- }
- title={tProjectDetails('satelliteAnalysis')}
- isSelected={selectedMode === 'satellite'}
- onClickHandler={() => setSelectedMode('satellite')}
- />
-
-
- }
- title={tMaps('fieldData')}
- isSelected={selectedMode === 'field'}
- onClickHandler={() => setSelectedMode('field')}
- />
-
-
- }
- title={tMaps('timeTravel')}
- isSelected={selectedMode === 'timeTravel'}
- onClickHandler={() => setSelectedMode('timeTravel')}
- />
-
- );
-};
-
-export default Tabs;
diff --git a/src/temp/TimeTravelDropdown/Dropdown.module.scss b/src/temp/TimeTravelDropdown/Dropdown.module.scss
deleted file mode 100644
index 53c66ba195..0000000000
--- a/src/temp/TimeTravelDropdown/Dropdown.module.scss
+++ /dev/null
@@ -1,64 +0,0 @@
-@import '../../theme/theme';
-.menuContainer {
- font-size: $fontXSmall;
-}
-.menuButton {
- border-radius: 8px;
- min-width: 150px;
- background: $light;
- height: 36px;
- display: flex;
- align-items: center;
- padding: 0 10px;
- justify-content: space-between;
- font-size: $fontXXSmall;
- gap: 4px;
- cursor: pointer;
- p {
- margin: 0 0 0 6px;
- }
- span {
- font-weight: 700;
- }
-
-}
-.menuButtonTitle {
- display: flex;
- align-items: center;
-}
-.menuItems {
- display: flex;
- width: fit-content;
- margin-top: 16px;
- border-radius: 8px;
- color: $grayFontColorNew;
-}
-.yearMenuContainer {
- border-radius: 8px 0 0 8px;
- background-color: $lightGrayBackgroundColorNew;
- padding:16px;
- display: flex;
- flex-direction: column;
- .unselectedMenuItem, .selectedMenuItem {
- opacity: 5;
- margin-top: 6px;
- cursor: pointer;
- }
-}
-.sourceMenuContainer {
- background: $light;
- padding: 16px;
- border-radius: 0px 8px 8px 0;
- .unselectedMenuItem, .selectedMenuItem {
- list-style: none;
- margin-top: 6px;
- min-width: 67px;
- cursor: pointer;
- }
-}
-
-
-.selectedMenuItem {
- color: #333; //to be replaced
- text-shadow: 0 0 0.01px;
-}
diff --git a/src/temp/TimeTravelDropdown/index.tsx b/src/temp/TimeTravelDropdown/index.tsx
deleted file mode 100644
index a94fcf2c7b..0000000000
--- a/src/temp/TimeTravelDropdown/index.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import React, { useState } from 'react';
-import styles from './Dropdown.module.scss';
-import CalendarIcon from '../icons/CalendarIcon';
-import DropdownDownArrow from '../icons/DropdownDownArrow';
-import DropdownUpArrow from '../icons/DropdownUpArrow';
-
-interface TimeTravelDropdownProps {
- labelYear: string;
- labelSource: string;
- yearList: string[];
- sourceList: string[];
- isOpen: boolean;
-}
-
-const TimeTravelDropdown = ({
- labelYear,
- labelSource,
- yearList,
- sourceList,
- isOpen,
-}: TimeTravelDropdownProps) => {
- const [selectedYear, setSelectedYear] = useState(labelYear);
- const [selectedSource, setSelectedSource] = useState(labelSource);
- const [isMenuOpen, setIsMenuOpen] = useState(isOpen);
- const handleChangeYear = (year: string) => {
- setSelectedYear(year);
- };
-
- const handleChangeSource = (source: string) => {
- setSelectedSource(source);
- };
-
- const styleForSelectedOption = (
- selectedOption: string,
- labelValue: string
- ) => {
- if (selectedOption.toLowerCase() === labelValue.toLowerCase()) {
- return true;
- } else {
- return false;
- }
- };
-
- return (
-
-
- {isMenuOpen && (
-
-
- {yearList?.map((year, index) => (
-
- ))}
-
-
- {sourceList?.map((source, index) => (
- - handleChangeSource(source)}
- className={`${
- styleForSelectedOption(source, selectedSource)
- ? styles.selectedMenuItem
- : styles.unselectedMenuItem
- }`}
- >
- {source}
-
- ))}
-
-
- )}
-
- );
-};
-
-export default TimeTravelDropdown;
diff --git a/src/temp/icons/FieldDataIcon.tsx b/src/temp/icons/FieldDataIcon.tsx
deleted file mode 100644
index 2c78dc2d76..0000000000
--- a/src/temp/icons/FieldDataIcon.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import type { IconProps } from '../../features/common/types/common';
-
-import React from 'react';
-
-const FieldDataIcon = ({ color, width, height }: IconProps) => {
- return (
-
- );
-};
-
-export default FieldDataIcon;
diff --git a/src/theme/themeProperties.ts b/src/theme/themeProperties.ts
index 1897a766ee..562dc00cc7 100644
--- a/src/theme/themeProperties.ts
+++ b/src/theme/themeProperties.ts
@@ -68,7 +68,7 @@ const themeProperties = {
disabledFontColor: '#909090',
tabBackgroundColor: '#f4ffec',
grayFontColorNew: '#4F4F4F',
- lightGrayBackgroundColorNew: '#d9d9d980',
+ lightGrayBackgroundColorNew: '#d9d9d9',
boldFontColorNew: '#2F3336',
selectedMenuItemColorNew: '#333',
dividerColorNew: '#BDBDBD',
diff --git a/src/utils/mapsV2/timeTravel.ts b/src/utils/mapsV2/timeTravel.ts
new file mode 100644
index 0000000000..e2af537d4c
--- /dev/null
+++ b/src/utils/mapsV2/timeTravel.ts
@@ -0,0 +1,138 @@
+import type { WaybackItem } from '@vannizhang/wayback-core';
+import type { Point } from 'geojson';
+
+// To remove timeTravelConfig (along with json file) once we remove the old maps code
+import timeTravelConfig from '../../../public/data/maps/time-travel.json';
+import { getWaybackItemsWithLocalChanges } from '@vannizhang/wayback-core';
+import { cacheKeyPrefix } from '../constants/cacheKeyPrefix';
+import { getCachedData } from '../../server/utils/cache';
+
+const SOURCE_NAMES = ['esri'] as const;
+
+export type SourceName = (typeof SOURCE_NAMES)[number];
+
+type SingleYearData = { year: string; raster: string };
+
+export type TimeTravelConfig = {
+ [key in SourceName]?: SingleYearData[];
+};
+
+const SOURCE_BASE_URLS: Record = {
+ esri: 'https://wayback.maptiles.arcgis.com/arcgis/rest/services/World_Imagery/WMTS/1.0.0/default028mm/MapServer/tile',
+};
+
+// To remove once we remove the old maps code
+/** Process data for all time travel sources */
+export const getTimeTravelConfig = (): TimeTravelConfig => {
+ const result: TimeTravelConfig = {};
+
+ for (const source of SOURCE_NAMES) {
+ const sourceData = timeTravelConfig[source]?.wayback;
+ if (!sourceData) continue;
+
+ result[source] = [];
+
+ for (const [year, data] of Object.entries(sourceData)) {
+ if (data?.id && data.id.length > 0) {
+ const url = `${SOURCE_BASE_URLS[source]}/${data.id}/{z}/{y}/{x}`;
+ result[source].push({ year, raster: url });
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Converts WMTS URL format ({level}/{row}/{col}) to standard tile format (z/y/x)
+ */
+const convertToZYXFormat = (url: string): string => {
+ return url
+ .replace('{level}', '{z}')
+ .replace('{row}', '{y}')
+ .replace('{col}', '{x}');
+};
+
+export interface SingleYearTimeTravelData {
+ year: string;
+ rasterUrl: string;
+}
+
+const getLatestByYear = (items: WaybackItem[]): SingleYearTimeTravelData[] => {
+ const intermediate = items.reduce<
+ Record
+ >((acc, item) => {
+ const year = new Date(item.releaseDatetime).getFullYear().toString();
+ const existing = acc[year];
+
+ if (!existing || item.releaseDatetime > existing.timestamp) {
+ acc[year] = {
+ rasterUrl: convertToZYXFormat(item.itemURL),
+ timestamp: item.releaseDatetime,
+ };
+ }
+
+ return acc;
+ }, {});
+
+ // Transform to array format
+ return Object.entries(intermediate).map(([year, item]) => ({
+ year,
+ rasterUrl: item.rasterUrl,
+ }));
+};
+
+export type ProjectTimeTravelSources = {
+ [key in SourceName]?: SingleYearTimeTravelData[];
+};
+
+export type ProjectTimeTravelConfig = {
+ projectId: string;
+ sources: ProjectTimeTravelSources | null;
+};
+
+export const getProjectTimeTravelConfig = async (
+ projectId: string,
+ projectPointGeometry: Point
+): Promise => {
+ const CACHE_KEY = `${cacheKeyPrefix}_time-travel_${projectId}`;
+ const CACHE_TIME_IN_SECONDS = 60 * 60 * 24 * 30; // cached for 30 days
+
+ async function fetchTimeTravelData(): Promise {
+ if (
+ !Array.isArray(projectPointGeometry?.coordinates) ||
+ projectPointGeometry.coordinates.length !== 2
+ ) {
+ throw new Error('Invalid project point geometry');
+ }
+
+ const esriWaybackItems = await getWaybackItemsWithLocalChanges(
+ {
+ longitude: projectPointGeometry.coordinates[0],
+ latitude: projectPointGeometry.coordinates[1],
+ },
+ 13 //TODO - confirm zoom level and update
+ );
+
+ if (esriWaybackItems.length === 0) {
+ return { projectId: projectId, sources: null };
+ } else {
+ return {
+ projectId: projectId,
+ sources: { esri: getLatestByYear(esriWaybackItems) },
+ };
+ }
+ }
+
+ try {
+ return await getCachedData(
+ CACHE_KEY,
+ fetchTimeTravelData,
+ CACHE_TIME_IN_SECONDS
+ );
+ } catch (err) {
+ console.error('Error fetching time travel data:', err);
+ // Return empty config on error to gracefully degrade
+ return null;
+ }
+};
diff --git a/src/utils/projectV2.ts b/src/utils/projectV2.ts
index 099d5d3d53..24024788fd 100644
--- a/src/utils/projectV2.ts
+++ b/src/utils/projectV2.ts
@@ -282,3 +282,19 @@ export const getDeviceType = (): MobileOs => {
if (/iPad|iPhone|iPod/.test(userAgent)) return 'ios';
return undefined;
};
+
+/**
+ * Checks if the "Firealert Fires" feature is enabled via env variable or query string.
+ * @returns boolean, Wheather this feature is enabled or not
+ */
+export function isFirealertFiresEnabled() {
+ const isEnvVariableEnabled =
+ process.env.NEXT_PUBLIC_ENABLE_FIREALERT_FIRES?.trim().toLowerCase() ===
+ 'true';
+ const isQueryStringEnabled =
+ new URLSearchParams(window.location.search)
+ .get('fa-fires')
+ ?.toLowerCase() === 'true';
+
+ return isEnvVariableEnabled || isQueryStringEnabled;
+}