-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Netmanager-app Map Page Integration #2482
Netmanager-app Map Page Integration #2482
Conversation
📝 WalkthroughWalkthroughA new map feature has been integrated into the netmanager-app. The pull request introduces the Changes
Sequence Diagram(s)sequenceDiagram
participant U as User
participant NM as Netmap
participant NMM as NetManagerMap
participant API as MapData API
U->>NM: Open Map Page
NM->>NMM: Render map component
NMM->>API: Request air quality data
API-->>NMM: Return GeoJSON data
U->>NMM: Perform search / interact with map
NMM->>API: Fetch suggestions and location data
API-->>NMM: Return suggestions
NMM->>U: Display interactive map with popups
Suggested labels
Suggested reviewers
Poem
Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
✨ Finishing Touches
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
|
GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
---|---|---|---|---|---|
- | - | Generic High Entropy Secret | 769b61a | netmanager-app/components/NetManagerMap/page.tsx | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secret safely. Learn here the best practices.
- Revoke and rotate this secret.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (3)
netmanager-app/components/NetManagerMap/page.tsx (1)
65-107
: Optimize event listener cleanup.The mouseenter/mouseleave event listeners should be properly cleaned up to prevent memory leaks.
Add cleanup logic:
+useEffect(() => { + const map = mapRef.current; + if (!map) return; + + const onMouseEnter = (e: mapboxgl.MapMouseEvent) => { + // Your existing mouseenter logic + }; + + const onMouseLeave = () => { + // Your existing mouseleave logic + }; + + map.on('mouseenter', 'data-layer', onMouseEnter); + map.on('mouseleave', 'data-layer', onMouseLeave); + + return () => { + map.off('mouseenter', 'data-layer', onMouseEnter); + map.off('mouseleave', 'data-layer', onMouseLeave); + }; +}, []);🧰 Tools
🪛 Biome (1.9.4)
[error] 71-71: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
netmanager-app/components/sidebar.tsx (1)
464-464
: Enhance collapse animation transition.The collapse animation could be smoother with CSS transitions.
Add transition properties:
-<ChevronRight onClick={handleCollapsible} className={` ${navcollapse?"drop":"revert" } flex cursor-pointer -ml-3 bg-white text-black border border-3 border-gray-200 rounded-full drop-shadow-lg mt-10 `}/> +<ChevronRight + onClick={handleCollapsible} + className={` + ${navcollapse ? "drop" : "revert"} + flex cursor-pointer -ml-3 bg-white text-black + border border-3 border-gray-200 rounded-full + drop-shadow-lg mt-10 + transition-all duration-300 ease-in-out + `} +/>netmanager-app/app/globals.css (1)
82-84
: Clarify the intent of the.revert
class.
While the transition effect is applied, if the purpose is to reverse the rotation applied by.drop
, consider explicitly setting a transform (e.g.,transform: rotate(0deg)
), which would ensure a consistent and predictable reverse animation.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (8)
netmanager-app/package-lock.json
is excluded by!**/package-lock.json
netmanager-app/public/images/map/GoodAir.png
is excluded by!**/*.png
netmanager-app/public/images/map/Hazardous.png
is excluded by!**/*.png
netmanager-app/public/images/map/Moderate.png
is excluded by!**/*.png
netmanager-app/public/images/map/Unhealthy.png
is excluded by!**/*.png
netmanager-app/public/images/map/UnhealthySG.png
is excluded by!**/*.png
netmanager-app/public/images/map/UnknownAQ.png
is excluded by!**/*.png
netmanager-app/public/images/map/VeryUnhealthy.png
is excluded by!**/*.png
📒 Files selected for processing (5)
netmanager-app/app/(authenticated)/network-map/page.tsx
(1 hunks)netmanager-app/app/globals.css
(1 hunks)netmanager-app/components/NetManagerMap/page.tsx
(1 hunks)netmanager-app/components/sidebar.tsx
(11 hunks)netmanager-app/package.json
(1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
netmanager-app/components/NetManagerMap/page.tsx
[error] 71-71: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 158-163: This block statement doesn't serve any purpose and can be safely removed.
Standalone block statements without any block-level declarations are redundant in JavaScript and can be removed to simplify the code.
Safe fix: Remove redundant block.
(lint/complexity/noUselessLoneBlockStatements)
🔇 Additional comments (3)
netmanager-app/app/(authenticated)/network-map/page.tsx (1)
1-10
: Clean implementation of the wrapper component!The component follows React best practices with a clear separation of concerns.
netmanager-app/package.json (1)
42-42
:✅ Verification successful
Verify Mapbox GL version compatibility.
Let's check if version 3.10.0 is the latest stable version and compatible with the project's React version.
🌐 Web query:
What is the latest stable version of mapbox-gl? Are there any known compatibility issues with React 18?
💡 Result:
The latest stable version of mapbox-gl is v3.10.0, as indicated by its GitHub releases and official documentation[1][10]. This version includes features like data-driven theme properties, improved SVG rendering, and bug fixes for querying features and WebGL context loss[1].
Compatibility with React 18
Known Issues:
- Older versions of react-map-gl (the React wrapper for Mapbox GL JS) had compatibility issues with React 18 due to lifecycle method changes[8].
- The
react-map-gl
v7 beta and later versions resolve these issues[5][6].Workarounds:
- Use
react-map-gl
v7+ for full React 18 compatibility[6].- For projects stuck on older
react-map-gl
versions (e.g., v6), ensure proper cleanup of map instances inuseEffect
hooks to avoid memory leaks[8].For new projects, install via:
npm install react-map-gl@7 mapbox-glThis combination ensures compatibility with React 18 while leveraging Mapbox GL JS v3.10.0[6][7].
Citations:
- 1: https://github.com/mapbox/mapbox-gl-js/releases
- 2: https://blog.logrocket.com/using-mapbox-gl-js-react/
- 3: https://docs.mapbox.com/help/troubleshooting/mapbox-browser-support/
- 4: https://www.geoapify.com/mapbox-gl-new-license-and-6-free-alternatives/
- 5: cooperativeGestures mapOption doesn't work visgl/react-map-gl#1640
- 6: https://classic.yarnpkg.com/en/package/react-map-gl
- 7: https://docs.mapbox.com/mapbox-gl-js/guides/migrate-to-v3/
- 8: [Bug] React 18 compatibility visgl/deck.gl#7158
- 9: https://docs.mapbox.com/mapbox-gl-js/example/check-for-support/
- 10: https://docs.mapbox.com/mapbox-gl-js/guides/
Mapbox GL Version Verified
The dependency
"mapbox-gl": "^3.10.0"
innetmanager-app/package.json
is confirmed as the latest stable release. There are no direct compatibility issues with React 18 reported for Mapbox GL itself. However, if you're using thereact-map-gl
wrapper, please ensure you have upgraded to v7+ to avoid known lifecycle issues with older versions.netmanager-app/app/globals.css (1)
78-81
: Solid styling for the.drop
class.
The class clearly defines a 180° rotation with a smooth 0.2-second ease-in-out transition, which should work well for visually toggling elements.
const mapRef = useRef<mapboxgl.Map | null>(null); | ||
const [query, setQuery] = useState(""); | ||
const [locationId,setlocationId] =useState("") | ||
const [suggestions, setSuggestions] = useState<any[]>([]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add TypeScript types for better type safety.
The code uses any
type and lacks proper TypeScript interfaces. This reduces type safety and IDE support.
Add proper TypeScript interfaces:
interface Measurement {
_id: string;
site_id: string;
time: string;
siteDetails: {
name: string;
approximate_latitude: number;
approximate_longitude: number;
};
aqi_category: string;
aqi_color: string;
pm2_5: {
value: number;
};
}
interface GeoJsonFeature {
type: "Feature";
geometry: {
type: "Point";
coordinates: [number, number];
};
properties: {
id: string;
site_id: string;
time: string;
location_name: string;
aqi_category: string;
aqi_color: string;
value: number;
};
}
interface GeoJsonData {
type: "FeatureCollection";
features: GeoJsonFeature[];
}
Also applies to: 236-266
const [query, setQuery] = useState(""); | ||
const [locationId,setlocationId] =useState("") | ||
const [suggestions, setSuggestions] = useState<any[]>([]); | ||
const token = 'pk.eyJ1IjoiZWxpYWxpZ2h0IiwiYSI6ImNtNzJsMnZnbjBhajIyanIwN3A3eWY2YmUifQ.x0x411yjbETiJ-F8ebivHQ' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move Mapbox token to environment variables.
The Mapbox access token should not be hardcoded in the source code. This is a security risk as the token could be exposed in version control.
Move the token to environment variables:
-const token = 'pk.eyJ1IjoiZWxpYWxpZ2h0IiwiYSI6ImNtNzJsMnZnbjBhajIyanIwN3A3eWY2YmUifQ.x0x411yjbETiJ-F8ebivHQ'
+const token = process.env.NEXT_PUBLIC_MAPBOX_TOKEN
Add to your environment files:
NEXT_PUBLIC_MAPBOX_TOKEN=your_token_here
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const token = 'pk.eyJ1IjoiZWxpYWxpZ2h0IiwiYSI6ImNtNzJsMnZnbjBhajIyanIwN3A3eWY2YmUifQ.x0x411yjbETiJ-F8ebivHQ' | |
const token = process.env.NEXT_PUBLIC_MAPBOX_TOKEN |
const response = await fetch('https://staging-analytics.airqo.net/api/v2/devices/readings/map?token=NQ9PFPCJXNEREMUR'); | ||
const jsonData = await response.json(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance error handling for API calls.
The API call lacks proper error handling and uses a hardcoded token. Consider implementing retry logic and error boundaries.
Implement better error handling:
-const response = await fetch('https://staging-analytics.airqo.net/api/v2/devices/readings/map?token=NQ9PFPCJXNEREMUR');
-const jsonData = await response.json();
+try {
+ const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/devices/readings/map`, {
+ headers: {
+ 'Authorization': `Bearer ${sessionToken}`
+ }
+ });
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ const jsonData = await response.json();
+} catch (error) {
+ console.error('Failed to fetch map data:', error);
+ // Handle error state
+}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const response = await fetch('https://staging-analytics.airqo.net/api/v2/devices/readings/map?token=NQ9PFPCJXNEREMUR'); | |
const jsonData = await response.json(); | |
try { | |
const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/devices/readings/map`, { | |
headers: { | |
'Authorization': `Bearer ${sessionToken}` | |
} | |
}); | |
if (!response.ok) { | |
throw new Error(`HTTP error! status: ${response.status}`); | |
} | |
const jsonData = await response.json(); | |
} catch (error) { | |
console.error('Failed to fetch map data:', error); | |
// Handle error state | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (5)
netmanager-app/components/NetManagerMap/page.tsx (5)
72-77
: Simplify conditional logic.The feature check can be simplified and the empty block removed.
-const feature = e.features ? e.features[0] : null; -if (feature && feature.properties) { - -} - -if (!feature) return; +const feature = e.features?.[0]; +if (!feature?.properties) return;🧰 Tools
🪛 Biome (1.9.4)
[error] 73-73: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
160-164
: Remove redundant block statement.The block statement doesn't serve any purpose and can be safely removed.
-}{ - <div> - Loading Data - </div> -}🧰 Tools
🪛 Biome (1.9.4)
[error] 160-165: This block statement doesn't serve any purpose and can be safely removed.
Standalone block statements without any block-level declarations are redundant in JavaScript and can be removed to simplify the code.
Safe fix: Remove redundant block.(lint/complexity/noUselessLoneBlockStatements)
191-196
: Remove console.log statements and improve error handling.Console logs should be removed in production code, and error handling can be improved.
-console.log("data :",data.suggestions) if (data.suggestions) { setSuggestions(data.suggestions); } -console.log("Number of Suggesstions", suggestions.length) +// Consider adding error state handling here
222-237
: Add loading state for location selection.The location selection could benefit from a loading state to improve user experience.
+const [isLocationLoading, setIsLocationLoading] = useState(false); const handleLocationClick = (locationid: any) => { + setIsLocationLoading(true); fetch(`https://api.mapbox.com/search/searchbox/v1/retrieve/${locationid}?&access_token=${token}&session_token=${sessionToken}`) .then(response => response.json()) .then(data => { - console.log(data.features[0].geometry.coordinates) if (mapRef.current) { mapRef.current.flyTo({ center: data.features[0].geometry.coordinates, zoom: 12, essential: true }); } + setIsLocationLoading(false); }) - .catch(error => console.error("Error fetching location:", error)); + .catch(error => { + console.error("Error fetching location:", error); + setIsLocationLoading(false); + }); }
324-328
: Improve loading state UI.The loading state UI could be enhanced with a proper loading component.
-{ mapContainerRef ?( +{mapContainerRef ? ( <div ref={mapContainerRef} className="rounded-lg map-container w-full md:h-full"/> -):( - <div className='text-black'>Loading...</div> +) : ( + <div className="flex items-center justify-center h-full"> + <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div> + <span className="ml-2 text-black">Loading map...</span> + </div> )
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
netmanager-app/components/NetManagerMap/page.tsx
(1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
netmanager-app/components/NetManagerMap/page.tsx
[error] 73-73: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 160-165: This block statement doesn't serve any purpose and can be safely removed.
Standalone block statements without any block-level declarations are redundant in JavaScript and can be removed to simplify the code.
Safe fix: Remove redundant block.
(lint/complexity/noUselessLoneBlockStatements)
🔇 Additional comments (1)
netmanager-app/components/NetManagerMap/page.tsx (1)
13-13
: Add TypeScript types for better type safety.The code uses
any
type for suggestions array. This reduces type safety and IDE support.
try { | ||
const response = await fetch(`https://staging-analytics.airqo.net/api/v2/devices/readings/map?token=${AirQoToken}`); | ||
const jsonData = await response.json(); | ||
const geojsonData = ConvertToGeojson(jsonData); | ||
console.log('GeoJson Data : ',geojsonData) | ||
|
||
mapRef.current.addSource('data', { | ||
type: 'geojson', | ||
data: geojsonData | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance error handling for API calls and add loading state.
The API call could benefit from better error handling and a loading state indicator.
try {
+ const [isLoading, setIsLoading] = useState(false);
+ setIsLoading(true);
const response = await fetch(`https://staging-analytics.airqo.net/api/v2/devices/readings/map?token=${AirQoToken}`);
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
const jsonData = await response.json();
const geojsonData = ConvertToGeojson(jsonData);
- console.log('GeoJson Data : ',geojsonData)
+ setIsLoading(false);
mapRef.current.addSource('data', {
type: 'geojson',
data: geojsonData
});
Committable suggestion skipped: line range outside the PR's diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🔭 Outside diff range comments (1)
netmanager-app/lib/utils.ts (1)
13-44
:⚠️ Potential issueFix the array manipulation in
transformDataToGeoJson
.The function has a critical bug where
forEach
is incorrectly used instead ofpush
to add items to the features array. This will result in no features being added to the collection.Apply this fix:
- data.map((feature) => { + data.forEach((feature) => { if (filter(feature)) { - features.forEach({ + features.push({ type: "Feature", properties: { ...rest, ...feature }, geometry: { type: "Point", coordinates: (coordinateGetter && coordinateGetter(feature)) || [ feature[longitude] || 0, feature[latitude] || 0, ], }, }); } });🧰 Tools
🪛 Biome (1.9.4)
[error] 31-31: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
♻️ Duplicate comments (1)
netmanager-app/components/NetManagerMap/page.tsx (1)
52-61
: 🛠️ Refactor suggestionEnhance data fetching with proper error handling and loading states.
The current implementation could benefit from better error handling and user feedback during data loading.
Apply these improvements:
+const API_URL = process.env.NEXT_PUBLIC_API_URL ?? 'https://staging-analytics.airqo.net/api/v2'; +const [isDataLoading, setIsDataLoading] = useState(false); +const [error, setError] = useState<string | null>(null); try { + setIsDataLoading(true); + setError(null); - const response = await fetch(`https://staging-analytics.airqo.net/api/v2/devices/readings/map?token=${AirQoToken}`); + const response = await fetch(`${API_URL}/devices/readings/map?token=${AirQoToken}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } const jsonData = await response.json(); const geojsonData = ConvertToGeojson(jsonData); - console.log('GeoJson Data : ',geojsonData) mapRef.current.addSource('data', { type: 'geojson', data: geojsonData }); +} catch (error) { + setError(error instanceof Error ? error.message : 'Failed to fetch data'); + console.error('Error fetching GeoJSON:', error); +} finally { + setIsDataLoading(false); }
🧹 Nitpick comments (2)
netmanager-app/components/NetManagerMap/page.tsx (2)
39-47
: Improve map initialization with configuration options and loading state.Consider extracting map configuration and adding loading state management.
Apply these improvements:
+const DEFAULT_MAP_CONFIG = { + style: 'mapbox://styles/mapbox/navigation-day-v1', + center: [18.5, 3], + zoom: 3, +}; +const [isLoading, setIsLoading] = useState(true); useEffect(() => { mapboxgl.accessToken = token; if (mapContainerRef.current) { mapRef.current = new mapboxgl.Map({ container: mapContainerRef.current, - style:'mapbox://styles/mapbox/navigation-day-v1', - center: [18.5, 3], - zoom: 3 + ...DEFAULT_MAP_CONFIG }); + setIsLoading(false); }
237-299
: Enhance UI rendering with better accessibility and loading states.The UI could benefit from:
- ARIA labels for accessibility
- Better loading states
- Simplified conditional rendering
Apply these improvements:
return ( - <div className="flex flex-col-reverse md:flex md:flex-row min-h-screen md:h-screen -ml-5 "> + <div className="flex flex-col-reverse md:flex md:flex-row min-h-screen md:h-screen -ml-5" role="main"> <div className='flex flex-grow md:flex-grow-0 border rounded-lg md:w-[24%]'> <div className="flex flex-col border gap-2 p-1 rounded-lg w-full"> <div className="flex flex-col gap-3"> - <h1 className="font-bold">Net Manager Map</h1> + <h1 className="font-bold" id="map-title">Net Manager Map</h1> <div className="relative w-full"> <Input placeholder="Search all locations" type="text" value={query} - onChange={SearchSuggestions} + onChange={handleSearchInput} + aria-label="Search locations" name="Search" className="w-full pr-10" /> </div> </div> + {isLoading && <div className="flex justify-center p-4">Loading map...</div>} + {error && <div className="text-red-500 p-4">{error}</div>} <div className="flex w-full"> - {suggestions ? ( + {isDataLoading ? ( + <div className="bg-white w-full border border-gray-300 mt-1 rounded-md shadow-md p-2"> + Loading suggestions... + </div> + ) : suggestions.length > 0 ? ( <div id="search-suggestions" className="w-full" + role="listbox" + aria-label="Location suggestions" > {suggestions.map((item, index) => ( <div key={index} className="bg-white w-full border border-gray-300 mt-1 rounded-md shadow-md max-h-40 overflow-y-auto p-2 hover:bg-gray-200 cursor-pointer" onClick={() => handleLocationClick(item.mapbox_id)} + role="option" + aria-selected="false" > <h1 className="text">{item.name}</h1> <h1 className="text-gray-300">{item.place_formatted}</h1> </div> ))} </div> ) : ( - <div className="bg-white w-full border border-gray-300 mt-1 rounded-md shadow-md max-h-40 overflow-y-auto p-2 hover:bg-gray-200 cursor-pointer"> - Loading Suggestions... + <div className="bg-white w-full border border-gray-300 mt-1 rounded-md shadow-md p-2"> + No suggestions found </div> )} </div> </div> </div> </div> );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
platform/package-lock.json
is excluded by!**/package-lock.json
platform/yarn.lock
is excluded by!**/yarn.lock
,!**/*.lock
📒 Files selected for processing (2)
netmanager-app/components/NetManagerMap/page.tsx
(1 hunks)netmanager-app/lib/utils.ts
(1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
netmanager-app/components/NetManagerMap/page.tsx
[error] 74-74: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
netmanager-app/lib/utils.ts
Outdated
export const ConvertToGeojson=(data: any)=>{ | ||
return { | ||
type: "FeatureCollection" as const, | ||
features: data.measurements.map((item: any) => ({ | ||
type: "Feature", | ||
geometry: { | ||
type: "Point", | ||
coordinates: [ | ||
item.siteDetails.approximate_longitude, | ||
item.siteDetails.approximate_latitude | ||
] | ||
}, | ||
properties: { | ||
id: item._id, | ||
site_id:item.site_id, | ||
time: new Date(item.time).toLocaleDateString("en-US", { | ||
weekday: "long", | ||
year: "numeric", | ||
month: "long", | ||
day: "numeric" | ||
}), | ||
location_name:item.siteDetails.name, | ||
aqi_category: item.aqi_category, | ||
aqi_color:item.aqi_color.startsWith("#") ? item.aqi_color : `#${item.aqi_color}`, | ||
value:item.pm2_5 ?.value??0, | ||
} | ||
})) | ||
} | ||
|
||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance type safety and error handling in ConvertToGeojson
.
The function could benefit from the following improvements:
- Type safety using proper interfaces
- Error handling for undefined properties
- Consistent formatting
Apply these improvements:
+interface SiteDetails {
+ approximate_longitude: number;
+ approximate_latitude: number;
+ name: string;
+}
+interface Measurement {
+ _id: string;
+ site_id: string;
+ time: string;
+ siteDetails: SiteDetails;
+ aqi_category: string;
+ aqi_color: string;
+ pm2_5?: { value: number };
+}
+interface GeoJsonData {
+ measurements: Measurement[];
+}
-export const ConvertToGeojson=(data: any)=>{
+export const ConvertToGeojson = (data: GeoJsonData) => {
+ if (!data?.measurements?.length) {
+ throw new Error('Invalid data format: measurements array is required');
+ }
return {
type: "FeatureCollection" as const,
features: data.measurements.map((item) => ({
type: "Feature",
geometry: {
type: "Point",
coordinates: [
- item.siteDetails.approximate_longitude,
- item.siteDetails.approximate_latitude
+ item.siteDetails?.approximate_longitude ?? 0,
+ item.siteDetails?.approximate_latitude ?? 0
]
},
properties: {
id: item._id,
site_id: item.site_id,
time: new Date(item.time).toLocaleDateString("en-US", {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric"
}),
- location_name: item.siteDetails.name,
+ location_name: item.siteDetails?.name ?? 'Unknown Location',
aqi_category: item.aqi_category,
- aqi_color: item.aqi_color.startsWith("#") ? item.aqi_color : `#${item.aqi_color}`,
+ aqi_color: item.aqi_color?.startsWith("#") ? item.aqi_color : `#${item.aqi_color ?? '000000'}`,
value: item.pm2_5?.value ?? 0,
}
}))
};
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export const ConvertToGeojson=(data: any)=>{ | |
return { | |
type: "FeatureCollection" as const, | |
features: data.measurements.map((item: any) => ({ | |
type: "Feature", | |
geometry: { | |
type: "Point", | |
coordinates: [ | |
item.siteDetails.approximate_longitude, | |
item.siteDetails.approximate_latitude | |
] | |
}, | |
properties: { | |
id: item._id, | |
site_id:item.site_id, | |
time: new Date(item.time).toLocaleDateString("en-US", { | |
weekday: "long", | |
year: "numeric", | |
month: "long", | |
day: "numeric" | |
}), | |
location_name:item.siteDetails.name, | |
aqi_category: item.aqi_category, | |
aqi_color:item.aqi_color.startsWith("#") ? item.aqi_color : `#${item.aqi_color}`, | |
value:item.pm2_5 ?.value??0, | |
} | |
})) | |
} | |
} | |
interface SiteDetails { | |
approximate_longitude: number; | |
approximate_latitude: number; | |
name: string; | |
} | |
interface Measurement { | |
_id: string; | |
site_id: string; | |
time: string; | |
siteDetails: SiteDetails; | |
aqi_category: string; | |
aqi_color: string; | |
pm2_5?: { value: number }; | |
} | |
interface GeoJsonData { | |
measurements: Measurement[]; | |
} | |
export const ConvertToGeojson = (data: GeoJsonData) => { | |
if (!data?.measurements?.length) { | |
throw new Error('Invalid data format: measurements array is required'); | |
} | |
return { | |
type: "FeatureCollection" as const, | |
features: data.measurements.map((item) => ({ | |
type: "Feature", | |
geometry: { | |
type: "Point", | |
coordinates: [ | |
item.siteDetails?.approximate_longitude ?? 0, | |
item.siteDetails?.approximate_latitude ?? 0 | |
] | |
}, | |
properties: { | |
id: item._id, | |
site_id: item.site_id, | |
time: new Date(item.time).toLocaleDateString("en-US", { | |
weekday: "long", | |
year: "numeric", | |
month: "long", | |
day: "numeric" | |
}), | |
location_name: item.siteDetails?.name ?? 'Unknown Location', | |
aqi_category: item.aqi_category, | |
aqi_color: item.aqi_color?.startsWith("#") ? item.aqi_color : `#${item.aqi_color ?? '000000'}`, | |
value: item.pm2_5?.value ?? 0, | |
} | |
})) | |
}; | |
}; |
const SearchSuggestions=(e: React.ChangeEvent<HTMLInputElement>)=>{ | ||
const value = e.target.value; | ||
setQuery(value); | ||
|
||
if (value.trim() === "") { | ||
setSuggestions([]); | ||
if (mapRef.current) { | ||
mapRef.current.flyTo({ | ||
center: [18.5,5], | ||
zoom: 3.0, | ||
essential: true | ||
}); | ||
} | ||
return; | ||
} | ||
const GetSuggestions=(latitude?: number, longitude?: number)=>{ | ||
|
||
|
||
if (latitude !== undefined && longitude !== undefined) { | ||
const proximityParam = `${longitude},${latitude}`; | ||
fetch(`https://api.mapbox.com/search/searchbox/v1/suggest?q=${value.toLowerCase()}&access_token=${token}&proximity=${proximityParam}&session_token=${sessionToken}`) | ||
.then(response => response.json()) | ||
|
||
.then(data => { | ||
console.log("data :",data.suggestions) | ||
if (data.suggestions) { | ||
setSuggestions(data.suggestions); | ||
} | ||
console.log("Number of Suggesstions", suggestions.length) | ||
}) | ||
.catch(error => console.error("Error fetching suggestions:", error)); | ||
} | ||
} | ||
const fetchUserLocation = () => { | ||
if (navigator.geolocation) { | ||
navigator.geolocation.getCurrentPosition( | ||
(position) => { | ||
const { latitude, longitude } = position.coords; | ||
console.log("User Location:", latitude, longitude); | ||
GetSuggestions(latitude, longitude); | ||
}, | ||
(error) => { | ||
console.error("Error getting user location:", error); | ||
GetSuggestions(); | ||
} | ||
); | ||
} else { | ||
console.error("Geolocation is not supported by this browser."); | ||
GetSuggestions(); | ||
} | ||
}; | ||
fetchUserLocation(); | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Optimize search functionality with debouncing and type safety.
The search implementation could benefit from:
- Debouncing to prevent excessive API calls
- Type safety for suggestions
- Removal of console.log statements
Apply these improvements:
+interface MapboxSuggestion {
+ mapbox_id: string;
+ name: string;
+ place_formatted: string;
+}
+const useDebounce = <T>(value: T, delay: number): T => {
+ const [debouncedValue, setDebouncedValue] = useState<T>(value);
+
+ useEffect(() => {
+ const handler = setTimeout(() => {
+ setDebouncedValue(value);
+ }, delay);
+
+ return () => {
+ clearTimeout(handler);
+ };
+ }, [value, delay]);
+
+ return debouncedValue;
+};
-const [suggestions, setSuggestions] = useState<any[]>([]);
+const [suggestions, setSuggestions] = useState<MapboxSuggestion[]>([]);
+const debouncedQuery = useDebounce(query, 300);
-const SearchSuggestions=(e: React.ChangeEvent<HTMLInputElement>)=>{
+const handleSearchInput = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setQuery(value);
};
+useEffect(() => {
if (value.trim() === "") {
setSuggestions([]);
if (mapRef.current) {
mapRef.current.flyTo({
center: [18.5,5],
zoom: 3.0,
essential: true
});
}
return;
}
fetchUserLocation();
-}, [debouncedQuery]);
Committable suggestion skipped: line range outside the PR's diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 9
🔭 Outside diff range comments (2)
netmanager-app/public/icons/map/gpsIcon.js (1)
1-24
: 🛠️ Refactor suggestionCreate a shared base component for SVG icons.
There's significant code duplication between icon components. Consider creating a base SVG icon component.
// components/icons/base/BaseSvgIcon.tsx interface BaseSvgIconProps { width?: string; height?: string; fill?: string; path: string; viewBox?: string; } export const BaseSvgIcon: React.FC<BaseSvgIconProps> = ({ width = '24', height = '24', fill = '#536A87', path, viewBox = '0 0 24 24' }) => ( <svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox={viewBox} fill="none" > <path d={path} stroke={fill} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> </svg> ); // public/icons/map/GpsIcon.tsx import { BaseSvgIcon } from '@/components/icons/base/BaseSvgIcon'; const GPS_ICON_PATH = "M20 12C20 16.4183 16.4183 20 12 20M20 12C20 7.58172 16.4183 4 12 4M20 12H22M12 20C7.58172 20 4 16.4183 4 12M12 20V22M4 12C4 7.58172 7.58172 4 12 4M4 12H2M12 4V2M15 12C15 13.6569 13.6569 15 12 15C10.3431 15 9 13.6569 9 12C9 10.3431 10.3431 9 12 9C13.6569 9 15 10.3431 15 12Z"; export const GpsIcon: React.FC<Omit<BaseSvgIconProps, 'path'>> = (props) => ( <BaseSvgIcon {...props} path={GPS_ICON_PATH} /> );netmanager-app/public/icons/map/refreshIcon.js (1)
1-24
: 🛠️ Refactor suggestionApply shared icon component pattern.
Use the same BaseSvgIcon pattern suggested for the GPS icon.
// public/icons/map/RefreshIcon.tsx import { BaseSvgIcon } from '@/components/icons/base/BaseSvgIcon'; const REFRESH_ICON_PATH = "M20.452 12.8927C20.1742 15.5026 18.6954 17.9483 16.2484 19.3611C12.1829 21.7083 6.98442 20.3153 4.63721 16.2499L4.38721 15.8168M3.54515 11.1066C3.82295 8.49674 5.30174 6.05102 7.74873 4.63825C11.8142 2.29104 17.0127 3.68398 19.3599 7.74947L19.6099 8.18248M3.49219 18.0657L4.22424 15.3336L6.95629 16.0657M17.0414 7.93364L19.7735 8.66569L20.5055 5.93364"; export const RefreshIcon: React.FC<Omit<BaseSvgIconProps, 'path'>> = (props) => ( <BaseSvgIcon {...props} path={REFRESH_ICON_PATH} /> );
♻️ Duplicate comments (1)
netmanager-app/components/NetManagerMap/page.tsx (1)
181-234
: 🛠️ Refactor suggestionOptimize search functionality with debouncing and type safety.
The search implementation could benefit from debouncing to prevent excessive API calls and proper TypeScript types for better type safety.
See previous review comment about implementing debouncing and adding proper TypeScript types for the search functionality.
🧹 Nitpick comments (9)
netmanager-app/public/icons/map/upArrow.js (1)
1-24
: Consider creating a reusable icon component.Given the similar structure across icon components, consider creating a reusable base component to reduce duplication and maintain consistency.
- Create a base IconComponent:
// components/common/Icon.tsx interface IconProps { name: 'up-arrow' | 'down-arrow' | 'plus' | 'minus'; width?: string; height?: string; fill?: string; } const PATHS = { 'up-arrow': "M18 15L12 9L6 15", 'down-arrow': "M6 9L12 15L18 9", 'plus': "M12 5V19M5 12H19", 'minus': "M5 12H19" }; export const Icon = ({ name, width = '24', height = '24', fill = '#536A87' }: IconProps) => ( <svg xmlns="http://www.w3.org/2000/svg" width={width} height={height} viewBox="0 0 24 24" fill="none" role="img" aria-label={name} > <path d={PATHS[name]} stroke={fill} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> </svg> );
- Then use it like this:
<Icon name="up-arrow" />For the immediate fix:
-const upArrow = ({ width, height, fill }) => { +const UpArrow = ({ width = '24', height = '24', fill = '#536A87' }) => { return ( <svg xmlns="http://www.w3.org/2000/svg" width={width || '24'} height={height || '24'} viewBox="0 0 24 24" fill="none" + role="img" + aria-label="up arrow" > <path d="M18 15L12 9L6 15" - stroke={fill || '#536A87'} + stroke={fill} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" /> </svg> ); };netmanager-app/public/icons/map/downArrow.js (1)
1-24
: Add tests for icon components.Consider adding tests to ensure icons render correctly with different props and maintain accessibility standards.
Here's a suggested test structure:
// __tests__/components/common/Icon.test.tsx import { render, screen } from '@testing-library/react'; import { Icon } from '../components/common/Icon'; describe('Icon', () => { it('renders with default props', () => { render(<Icon name="down-arrow" />); const svg = screen.getByRole('img', { name: 'down arrow' }); expect(svg).toBeInTheDocument(); expect(svg).toHaveAttribute('width', '24'); expect(svg).toHaveAttribute('height', '24'); }); it('renders with custom props', () => { render(<Icon name="down-arrow" width="32" height="32" fill="#000" />); const svg = screen.getByRole('img'); expect(svg).toHaveAttribute('width', '32'); expect(svg).toHaveAttribute('height', '32'); const path = svg.querySelector('path'); expect(path).toHaveAttribute('stroke', '#000'); }); });Would you like me to help set up the test infrastructure or create an issue to track this task?
netmanager-app/components/NetManagerMap/components/IconButton.tsx (2)
1-1
: Use more specific type imports.Import specific types instead of the entire React namespace.
-import type * as React from "react" +import type { FC, MouseEvent, ReactNode } from "react"
3-7
: Update interface to use more specific types.interface IconButtonProps { - onClick: (event: React.MouseEvent<HTMLButtonElement>) => void + onClick: (event: MouseEvent<HTMLButtonElement>) => void title: string - icon: React.ReactNode + icon: ReactNode }netmanager-app/public/icons/map/shareIcon.js (1)
3-21
: Convert to TypeScript and follow React naming conventions.The component should be converted to TypeScript and renamed to follow React naming conventions.
-const shareIcon = ({ width, height, fill }) => { +interface ShareIconProps { + width?: string; + height?: string; + fill?: string; +} + +const ShareIcon: React.FC<ShareIconProps> = ({ width, height, fill }) => {netmanager-app/components/NetManagerMap/components/zoom.tsx (2)
9-12
: Add TypeScript type definitions for class properties.The class properties should be properly typed for better type safety.
+interface MapRef extends mapboxgl.Map {} export class CustomZoomControl { + private map: MapRef | null; + private container: HTMLDivElement; + private zoomInButton: HTMLButtonElement; + private zoomOutButton: HTMLButtonElement; constructor() {
75-86
: Enhance URL state management with error handling.The URL state management could be more robust with proper error handling and validation.
updateUrlWithMapState = () => { if (!this.map) return; const center = this.map.getCenter(); const zoom = this.map.getZoom(); - if (!center || isNaN(zoom)) return; + try { + if (!center || isNaN(zoom)) { + throw new Error('Invalid map state'); + } - const url = new URL(window.location); - url.searchParams.set('lat', center.lat.toFixed(4)); - url.searchParams.set('lng', center.lng.toFixed(4)); - url.searchParams.set('zm', zoom.toFixed(2)); - window.history.pushState({}, '', url); + const url = new URL(window.location.href); + url.searchParams.set('lat', center.lat.toFixed(4)); + url.searchParams.set('lng', center.lng.toFixed(4)); + url.searchParams.set('zm', zoom.toFixed(2)); + window.history.pushState({ lat: center.lat, lng: center.lng, zoom }, '', url); + } catch (error) { + console.error('Failed to update URL with map state:', error); + } };netmanager-app/lib/utils.ts (1)
81-115
: Improve error handling and type safety in useRefreshMap.The function should have proper TypeScript types and better error handling.
+interface ToastMessage { + message: string; + type: 'success' | 'error'; + bgColor: string; +} + export const useRefreshMap = ( - setToastMessage, - mapRef, - dispatch, - selectedNode, + setToastMessage: (message: ToastMessage) => void, + mapRef: React.RefObject<mapboxgl.Map>, + dispatch: Function, + selectedNode: any ) => useCallback(() => { const map = mapRef.current; if (map) { try { + if (!map.getStyle()?.sprite) { + throw new Error('Invalid map style'); + } const originalStyle = map.getStyle().sprite.split('/').slice(0, -1).join('/') + '/style.json';netmanager-app/components/NetManagerMap/page.tsx (1)
73-73
: Remove console.log statements.There are several console.log statements throughout the code that should be removed or replaced with proper logging for production.
Consider using a proper logging library or removing these debug statements:
- Line 73:
console.log('GeoJson Data : ',geojsonData)
- Line 205:
console.log("data :",data.suggestions)
- Line 209:
console.log("Number of Suggesstions", suggestions.length)
- Line 219:
console.log("User Location:", latitude, longitude)
- Line 223:
console.error("Error getting user location:", error)
- Line 228:
console.error("Geolocation is not supported by this browser.")
- Line 240:
console.log(data.features[0].geometry.coordinates)
Also applies to: 205-205, 209-209, 219-219, 223-223, 228-228, 240-240
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
netmanager-app/public/images/map/Invalid.png
is excluded by!**/*.png
📒 Files selected for processing (17)
netmanager-app/components.json
(1 hunks)netmanager-app/components/NetManagerMap/components/IconButton.tsx
(1 hunks)netmanager-app/components/NetManagerMap/components/LayerModal.js
(1 hunks)netmanager-app/components/NetManagerMap/components/zoom.tsx
(1 hunks)netmanager-app/components/NetManagerMap/data/constants.js
(1 hunks)netmanager-app/components/NetManagerMap/page.tsx
(1 hunks)netmanager-app/lib/utils.ts
(1 hunks)netmanager-app/public/icons/map/downArrow.js
(1 hunks)netmanager-app/public/icons/map/gpsIcon.js
(1 hunks)netmanager-app/public/icons/map/homeIcon.js
(1 hunks)netmanager-app/public/icons/map/layerIcon.js
(1 hunks)netmanager-app/public/icons/map/menuIcon.js
(1 hunks)netmanager-app/public/icons/map/minusIcon.js
(1 hunks)netmanager-app/public/icons/map/plusIcon.js
(1 hunks)netmanager-app/public/icons/map/refreshIcon.js
(1 hunks)netmanager-app/public/icons/map/shareIcon.js
(1 hunks)netmanager-app/public/icons/map/upArrow.js
(1 hunks)
✅ Files skipped from review due to trivial changes (2)
- netmanager-app/public/icons/map/layerIcon.js
- netmanager-app/components/NetManagerMap/data/constants.js
🧰 Additional context used
🪛 Biome (1.9.4)
netmanager-app/components/NetManagerMap/components/LayerModal.js
[error] 158-158: This property value named onMapDetailsSelect is later overwritten by an object member with the same name.
Overwritten with this value.
If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named onMapDetailsSelect
(lint/suspicious/noDuplicateObjectKeys)
[error] 159-159: This property value named onStyleSelect is later overwritten by an object member with the same name.
Overwritten with this value.
If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named onStyleSelect
(lint/suspicious/noDuplicateObjectKeys)
netmanager-app/components/NetManagerMap/page.tsx
[error] 91-91: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (4)
netmanager-app/public/icons/map/homeIcon.js (1)
3-21
: Convert to TypeScript and follow React naming conventions.The component should be converted to TypeScript and renamed to follow React naming conventions.
netmanager-app/lib/utils.ts (1)
46-74
: Enhance type safety and error handling in ConvertToGeojson.The function needs proper type definitions and error handling.
netmanager-app/components/NetManagerMap/page.tsx (1)
23-24
: LGTM! Good use of environment variables for tokens.The Mapbox and AirQo tokens are correctly stored in environment variables, which is a secure practice.
netmanager-app/components.json (1)
18-20
: LGTM! Well-structured alias additions.The new aliases for icons and images follow the existing pattern and improve code organization.
const minusIcon = ({ width, height, fill }) => { | ||
return ( | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
width={width || '24'} | ||
height={height || '24'} | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
> | ||
<path | ||
d="M5 12H19" | ||
stroke={fill || '#536A87'} | ||
strokeWidth="1.5" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
/> | ||
</svg> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Follow React component naming convention and enhance accessibility.
- Component names should start with uppercase (e.g.,
MinusIcon
) - Consider adding TypeScript or PropTypes for type safety
- Add accessibility attributes for screen readers
-const minusIcon = ({ width, height, fill }) => {
+const MinusIcon = ({ width = '24', height = '24', fill = '#536A87' }) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={width || '24'}
height={height || '24'}
viewBox="0 0 24 24"
fill="none"
+ role="img"
+ aria-label="minus"
>
<path
d="M5 12H19"
- stroke={fill || '#536A87'}
+ stroke={fill}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const minusIcon = ({ width, height, fill }) => { | |
return ( | |
<svg | |
xmlns="http://www.w3.org/2000/svg" | |
width={width || '24'} | |
height={height || '24'} | |
viewBox="0 0 24 24" | |
fill="none" | |
> | |
<path | |
d="M5 12H19" | |
stroke={fill || '#536A87'} | |
strokeWidth="1.5" | |
strokeLinecap="round" | |
strokeLinejoin="round" | |
/> | |
</svg> | |
); | |
}; | |
const MinusIcon = ({ width = '24', height = '24', fill = '#536A87' }) => { | |
return ( | |
<svg | |
xmlns="http://www.w3.org/2000/svg" | |
width={width || '24'} | |
height={height || '24'} | |
viewBox="0 0 24 24" | |
fill="none" | |
role="img" | |
aria-label="minus" | |
> | |
<path | |
d="M5 12H19" | |
stroke={fill} | |
strokeWidth="1.5" | |
strokeLinecap="round" | |
strokeLinejoin="round" | |
/> | |
</svg> | |
); | |
}; |
const plusIcon = ({ width, height, fill }) => { | ||
return ( | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
width={width || '24'} | ||
height={height || '24'} | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
> | ||
<path | ||
d="M12 5V19M5 12H19" | ||
stroke={fill || '#536A87'} | ||
strokeWidth="2" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
/> | ||
</svg> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Maintain consistency with other icons and enhance accessibility.
- Component name should start with uppercase (e.g.,
PlusIcon
) - Stroke width (2) differs from MinusIcon (1.5) - consider maintaining consistency
- Add accessibility attributes for screen readers
-const plusIcon = ({ width, height, fill }) => {
+const PlusIcon = ({ width = '24', height = '24', fill = '#536A87' }) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width={width || '24'}
height={height || '24'}
viewBox="0 0 24 24"
fill="none"
+ role="img"
+ aria-label="plus"
>
<path
d="M12 5V19M5 12H19"
- stroke={fill || '#536A87'}
+ stroke={fill}
- strokeWidth="2"
+ strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const plusIcon = ({ width, height, fill }) => { | |
return ( | |
<svg | |
xmlns="http://www.w3.org/2000/svg" | |
width={width || '24'} | |
height={height || '24'} | |
viewBox="0 0 24 24" | |
fill="none" | |
> | |
<path | |
d="M12 5V19M5 12H19" | |
stroke={fill || '#536A87'} | |
strokeWidth="2" | |
strokeLinecap="round" | |
strokeLinejoin="round" | |
/> | |
</svg> | |
); | |
}; | |
const PlusIcon = ({ width = '24', height = '24', fill = '#536A87' }) => { | |
return ( | |
<svg | |
xmlns="http://www.w3.org/2000/svg" | |
width={width || '24'} | |
height={height || '24'} | |
viewBox="0 0 24 24" | |
fill="none" | |
role="img" | |
aria-label="plus" | |
> | |
<path | |
d="M12 5V19M5 12H19" | |
stroke={fill} | |
strokeWidth="1.5" | |
strokeLinecap="round" | |
strokeLinejoin="round" | |
/> | |
</svg> | |
); | |
}; |
); | ||
}; | ||
|
||
export default menuIcon; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Update export to match new component name.
-export default menuIcon;
+export default MenuIcon;
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export default menuIcon; | |
export default MenuIcon; |
const menuIcon = ({ width, height, fill }) => { | ||
return ( | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
width={width || '24'} | ||
height={height || '24'} | ||
viewBox="0 0 24 24" | ||
fill="none" | ||
> | ||
<path | ||
d="M3 12H21M3 6H21M3 18H15" | ||
stroke={fill || '#536A87'} | ||
strokeWidth="1.5" | ||
strokeLinecap="round" | ||
strokeLinejoin="round" | ||
/> | ||
</svg> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Convert to TypeScript and follow React naming conventions.
The component should follow React naming conventions (PascalCase) and include TypeScript types for better type safety.
-const menuIcon = ({ width, height, fill }) => {
+interface MenuIconProps {
+ width?: string;
+ height?: string;
+ fill?: string;
+}
+
+const MenuIcon: React.FC<MenuIconProps> = ({
+ width = '24',
+ height = '24',
+ fill = '#536A87'
+}) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
- width={width || '24'}
- height={height || '24'}
+ width={width}
+ height={height}
viewBox="0 0 24 24"
fill="none"
>
<path
d="M3 12H21M3 6H21M3 18H15"
- stroke={fill || '#536A87'}
+ stroke={fill}
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const menuIcon = ({ width, height, fill }) => { | |
return ( | |
<svg | |
xmlns="http://www.w3.org/2000/svg" | |
width={width || '24'} | |
height={height || '24'} | |
viewBox="0 0 24 24" | |
fill="none" | |
> | |
<path | |
d="M3 12H21M3 6H21M3 18H15" | |
stroke={fill || '#536A87'} | |
strokeWidth="1.5" | |
strokeLinecap="round" | |
strokeLinejoin="round" | |
/> | |
</svg> | |
); | |
}; | |
interface MenuIconProps { | |
width?: string; | |
height?: string; | |
fill?: string; | |
} | |
const MenuIcon: React.FC<MenuIconProps> = ({ | |
width = '24', | |
height = '24', | |
fill = '#536A87' | |
}) => { | |
return ( | |
<svg | |
xmlns="http://www.w3.org/2000/svg" | |
width={width} | |
height={height} | |
viewBox="0 0 24 24" | |
fill="none" | |
> | |
<path | |
d="M3 12H21M3 6H21M3 18H15" | |
stroke={fill} | |
strokeWidth="1.5" | |
strokeLinecap="round" | |
strokeLinejoin="round" | |
/> | |
</svg> | |
); | |
}; |
<button | ||
onClick={onClick} | ||
title={title} | ||
className="inline-flex items-center justify-center p-2 md:p-3 mr-2 text-gray-600 rounded-full bg-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 shadow-md" | ||
> | ||
{icon} | ||
</button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance button accessibility and form handling.
Add explicit button type and aria-label for better accessibility.
<button
+ type="button"
onClick={onClick}
title={title}
+ aria-label={title}
className="inline-flex items-center justify-center p-2 md:p-3 mr-2 text-gray-600 rounded-full bg-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 shadow-md"
>
{icon}
</button>
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<button | |
onClick={onClick} | |
title={title} | |
className="inline-flex items-center justify-center p-2 md:p-3 mr-2 text-gray-600 rounded-full bg-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 shadow-md" | |
> | |
{icon} | |
</button> | |
<button | |
type="button" | |
onClick={onClick} | |
title={title} | |
aria-label={title} | |
className="inline-flex items-center justify-center p-2 md:p-3 mr-2 text-gray-600 rounded-full bg-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 shadow-md" | |
> | |
{icon} | |
</button> |
image: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, // Allow both string and object | ||
}) | ||
).isRequired, | ||
mapDetails: PropTypes.array, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance type safety for mapDetails prop.
The mapDetails
prop is defined as a generic array without shape validation, which could lead to runtime errors.
Apply this diff to add proper shape validation:
- mapDetails: PropTypes.array,
+ mapDetails: PropTypes.arrayOf(
+ PropTypes.shape({
+ name: PropTypes.string.isRequired,
+ image: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
+ })
+ ).isRequired,
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
mapDetails: PropTypes.array, | |
mapDetails: PropTypes.arrayOf( | |
PropTypes.shape({ | |
name: PropTypes.string.isRequired, | |
image: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, | |
}) | |
).isRequired, |
LayerModal.propTypes = { | ||
isOpen: PropTypes.bool.isRequired, | ||
onClose: PropTypes.func.isRequired, | ||
onMapDetailsSelect: PropTypes.func.isRequired, | ||
onStyleSelect: PropTypes.func.isRequired, | ||
mapStyles: PropTypes.arrayOf( | ||
PropTypes.shape({ | ||
name: PropTypes.string.isRequired, | ||
image: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, // Allow both string and object | ||
}) | ||
).isRequired, | ||
mapDetails: PropTypes.array, | ||
disabled: PropTypes.string, | ||
onMapDetailsSelect: PropTypes.func.isRequired, | ||
onStyleSelect: PropTypes.func.isRequired, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix duplicate PropTypes definitions.
The PropTypes for onMapDetailsSelect
and onStyleSelect
are defined twice. This can lead to confusion and potential issues.
Apply this diff to remove the duplicates:
LayerModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
onMapDetailsSelect: PropTypes.func.isRequired,
onStyleSelect: PropTypes.func.isRequired,
mapStyles: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string.isRequired,
image: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
})
).isRequired,
mapDetails: PropTypes.array,
disabled: PropTypes.string,
- onMapDetailsSelect: PropTypes.func.isRequired,
- onStyleSelect: PropTypes.func.isRequired,
};
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
LayerModal.propTypes = { | |
isOpen: PropTypes.bool.isRequired, | |
onClose: PropTypes.func.isRequired, | |
onMapDetailsSelect: PropTypes.func.isRequired, | |
onStyleSelect: PropTypes.func.isRequired, | |
mapStyles: PropTypes.arrayOf( | |
PropTypes.shape({ | |
name: PropTypes.string.isRequired, | |
image: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, // Allow both string and object | |
}) | |
).isRequired, | |
mapDetails: PropTypes.array, | |
disabled: PropTypes.string, | |
onMapDetailsSelect: PropTypes.func.isRequired, | |
onStyleSelect: PropTypes.func.isRequired, | |
}; | |
LayerModal.propTypes = { | |
isOpen: PropTypes.bool.isRequired, | |
onClose: PropTypes.func.isRequired, | |
onMapDetailsSelect: PropTypes.func.isRequired, | |
onStyleSelect: PropTypes.func.isRequired, | |
mapStyles: PropTypes.arrayOf( | |
PropTypes.shape({ | |
name: PropTypes.string.isRequired, | |
image: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, // Allow both string and object | |
}) | |
).isRequired, | |
mapDetails: PropTypes.array, | |
disabled: PropTypes.string, | |
}; |
🧰 Tools
🪛 Biome (1.9.4)
[error] 158-158: This property value named onMapDetailsSelect is later overwritten by an object member with the same name.
Overwritten with this value.
If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named onMapDetailsSelect
(lint/suspicious/noDuplicateObjectKeys)
[error] 159-159: This property value named onStyleSelect is later overwritten by an object member with the same name.
Overwritten with this value.
If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this property value named onStyleSelect
(lint/suspicious/noDuplicateObjectKeys)
useEffect(() => { | ||
if (mapStyles[0]) { | ||
setSelectedStyle(mapStyles[0]); | ||
} | ||
if (mapDetails[0]) { | ||
setSelectedMapDetail(mapDetails[0]); | ||
} | ||
}, [mapStyles, mapDetails]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add validation for empty arrays in useEffect.
The current implementation assumes that mapStyles
and mapDetails
arrays have at least one element, which could lead to runtime errors.
Apply this diff to add proper validation:
useEffect(() => {
- if (mapStyles[0]) {
+ if (mapStyles.length > 0) {
setSelectedStyle(mapStyles[0]);
}
- if (mapDetails[0]) {
+ if (mapDetails.length > 0) {
setSelectedMapDetail(mapDetails[0]);
}
}, [mapStyles, mapDetails]);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
useEffect(() => { | |
if (mapStyles[0]) { | |
setSelectedStyle(mapStyles[0]); | |
} | |
if (mapDetails[0]) { | |
setSelectedMapDetail(mapDetails[0]); | |
} | |
}, [mapStyles, mapDetails]); | |
useEffect(() => { | |
if (mapStyles.length > 0) { | |
setSelectedStyle(mapStyles[0]); | |
} | |
if (mapDetails.length > 0) { | |
setSelectedMapDetail(mapDetails[0]); | |
} | |
}, [mapStyles, mapDetails]); |
type: 'geojson', | ||
data: geojsonData | ||
}); | ||
const popup = new mapboxgl.Popup({ | ||
closeButton: false, | ||
closeOnClick: false, | ||
|
||
}); | ||
// Listen for when the mouse enters a feature in your 'data-layer' | ||
mapRef.current.on('mouseenter', 'data-layer', (e) => { | ||
if (mapRef.current) { | ||
mapRef.current.getCanvas().style.cursor = 'pointer'; | ||
} | ||
// Get the feature under the mouse pointer (the first feature). | ||
const feature = e.features ? e.features[0] : null; | ||
if (feature && feature.properties) { | ||
|
||
} | ||
|
||
if (!feature) return; | ||
|
||
const htmlContent = ` | ||
<div style="font-size: 14px; border-radius: 25px; background-color: white; padding: 10px; rgba(0, 0, 0, 0.2); max-width: full;"> | ||
<div>${feature.properties?.time ?? 'Unknown time'}</div><br> | ||
<div style="display: flex; justify-content: space-between;gap: 20px;"> | ||
<strong style="display: flex; ">${feature.properties?.location_name ?? 'Unknown Location'}</strong> | ||
<div style="display: flex;font-weight: bold; ">${feature.properties?.value.toFixed(2) ?? 'N/A'}µg/m³</div> | ||
</div> | ||
|
||
<div style=" display: flex;gap: 10px; "> | ||
<h1 style="font-weight: bold; color: ${feature.properties?.aqi_color ?? 'black'};">Air Quality is ${feature.properties?.aqi_category ?? 'N/A'}</h1> | ||
<img src="${AirQuality.goodair}" style="background-color: ${feature.properties?.aqi_color ?? 'green'};width: 30px; height: 30px;border-radius: 50%;font-size: 18px;"></img> | ||
</div> | ||
</div> | ||
`; | ||
|
||
// Set the popup at the feature's coordinates and add the HTML content. | ||
popup | ||
.setLngLat((feature.geometry as GeoJSON.Point).coordinates as [number, number]) | ||
.setHTML(htmlContent) | ||
if (mapRef.current) { | ||
popup.addTo(mapRef.current); | ||
} | ||
}); | ||
|
||
// When the mouse leaves the feature, remove the popup. | ||
mapRef.current.on('mouseleave', 'data-layer', () => { | ||
if (mapRef.current) { | ||
mapRef.current.getCanvas().style.cursor = ''; | ||
} | ||
popup.remove(); | ||
}); | ||
Object.entries(AirQuality).forEach(([key, url]) => { | ||
if (mapRef.current) { | ||
mapRef.current.loadImage(url, (error, image) => { | ||
if (error) throw error; | ||
|
||
if (mapRef.current && image && !mapRef.current.hasImage(key)) { | ||
mapRef.current.addImage(key, image); | ||
} | ||
}); | ||
} | ||
}); | ||
mapRef.current.addLayer({ | ||
'id': 'circle-layer', | ||
'type': 'circle', | ||
'source': 'data', | ||
'paint': { | ||
'circle-color': 'white', | ||
'circle-radius': 30, | ||
'circle-opacity': 1 | ||
} | ||
}); | ||
mapRef.current.addLayer({ | ||
'id': 'data-layer', | ||
'type': 'symbol', | ||
'source': 'data', | ||
'layout': { | ||
'icon-image': [ | ||
'match', | ||
['get', 'aqi_category'], | ||
'Good', 'goodair', | ||
'Moderate', 'moderate', | ||
'Unhealthy', 'unhealthy', | ||
'Unhealthy for Sensitive Groups', 'unhealthySG', | ||
'Hazardous','Hazardous', | ||
'Very Unhealthy', 'veryunhealthy', | ||
'unknownAQ' | ||
], | ||
'icon-size': 0.05, | ||
'icon-allow-overlap': true | ||
}, | ||
|
||
'filter': ['has', 'aqi_category'] | ||
}); | ||
|
||
|
||
} catch (error) { | ||
console.error('Error fetching GeoJSON:', error); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance error handling for API calls.
The current error handling could be improved to handle non-200 responses and network errors more gracefully.
Apply this diff to enhance error handling:
try {
+ if (!AirQoToken) {
+ throw new Error('AirQo token is not configured');
+ }
const response = await fetch(`https://staging-analytics.airqo.net/api/v2/devices/readings/map?token=${AirQoToken}`);
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
const jsonData = await response.json();
+ if (!jsonData || !Array.isArray(jsonData)) {
+ throw new Error('Invalid data format received from API');
+ }
const geojsonData = ConvertToGeojson(jsonData);
- console.log('GeoJson Data : ',geojsonData)
// ... rest of the code
} catch (error) {
- console.error('Error fetching GeoJSON:', error);
+ console.error('Error fetching or processing map data:', error);
+ // TODO: Add user-friendly error handling
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
try { | |
const response = await fetch(`https://staging-analytics.airqo.net/api/v2/devices/readings/map?token=${AirQoToken}`); | |
const jsonData = await response.json(); | |
const geojsonData = ConvertToGeojson(jsonData); | |
console.log('GeoJson Data : ',geojsonData) | |
mapRef.current.addSource('data', { | |
type: 'geojson', | |
data: geojsonData | |
}); | |
const popup = new mapboxgl.Popup({ | |
closeButton: false, | |
closeOnClick: false, | |
}); | |
// Listen for when the mouse enters a feature in your 'data-layer' | |
mapRef.current.on('mouseenter', 'data-layer', (e) => { | |
if (mapRef.current) { | |
mapRef.current.getCanvas().style.cursor = 'pointer'; | |
} | |
// Get the feature under the mouse pointer (the first feature). | |
const feature = e.features ? e.features[0] : null; | |
if (feature && feature.properties) { | |
} | |
if (!feature) return; | |
const htmlContent = ` | |
<div style="font-size: 14px; border-radius: 25px; background-color: white; padding: 10px; rgba(0, 0, 0, 0.2); max-width: full;"> | |
<div>${feature.properties?.time ?? 'Unknown time'}</div><br> | |
<div style="display: flex; justify-content: space-between;gap: 20px;"> | |
<strong style="display: flex; ">${feature.properties?.location_name ?? 'Unknown Location'}</strong> | |
<div style="display: flex;font-weight: bold; ">${feature.properties?.value.toFixed(2) ?? 'N/A'}µg/m³</div> | |
</div> | |
<div style=" display: flex;gap: 10px; "> | |
<h1 style="font-weight: bold; color: ${feature.properties?.aqi_color ?? 'black'};">Air Quality is ${feature.properties?.aqi_category ?? 'N/A'}</h1> | |
<img src="${AirQuality.goodair}" style="background-color: ${feature.properties?.aqi_color ?? 'green'};width: 30px; height: 30px;border-radius: 50%;font-size: 18px;"></img> | |
</div> | |
</div> | |
`; | |
// Set the popup at the feature's coordinates and add the HTML content. | |
popup | |
.setLngLat((feature.geometry as GeoJSON.Point).coordinates as [number, number]) | |
.setHTML(htmlContent) | |
if (mapRef.current) { | |
popup.addTo(mapRef.current); | |
} | |
}); | |
// When the mouse leaves the feature, remove the popup. | |
mapRef.current.on('mouseleave', 'data-layer', () => { | |
if (mapRef.current) { | |
mapRef.current.getCanvas().style.cursor = ''; | |
} | |
popup.remove(); | |
}); | |
Object.entries(AirQuality).forEach(([key, url]) => { | |
if (mapRef.current) { | |
mapRef.current.loadImage(url, (error, image) => { | |
if (error) throw error; | |
if (mapRef.current && image && !mapRef.current.hasImage(key)) { | |
mapRef.current.addImage(key, image); | |
} | |
}); | |
} | |
}); | |
mapRef.current.addLayer({ | |
'id': 'circle-layer', | |
'type': 'circle', | |
'source': 'data', | |
'paint': { | |
'circle-color': 'white', | |
'circle-radius': 30, | |
'circle-opacity': 1 | |
} | |
}); | |
mapRef.current.addLayer({ | |
'id': 'data-layer', | |
'type': 'symbol', | |
'source': 'data', | |
'layout': { | |
'icon-image': [ | |
'match', | |
['get', 'aqi_category'], | |
'Good', 'goodair', | |
'Moderate', 'moderate', | |
'Unhealthy', 'unhealthy', | |
'Unhealthy for Sensitive Groups', 'unhealthySG', | |
'Hazardous','Hazardous', | |
'Very Unhealthy', 'veryunhealthy', | |
'unknownAQ' | |
], | |
'icon-size': 0.05, | |
'icon-allow-overlap': true | |
}, | |
'filter': ['has', 'aqi_category'] | |
}); | |
} catch (error) { | |
console.error('Error fetching GeoJSON:', error); | |
} | |
try { | |
if (!AirQoToken) { | |
throw new Error('AirQo token is not configured'); | |
} | |
const response = await fetch(`https://staging-analytics.airqo.net/api/v2/devices/readings/map?token=${AirQoToken}`); | |
if (!response.ok) { | |
throw new Error(`HTTP error! status: ${response.status}`); | |
} | |
const jsonData = await response.json(); | |
if (!jsonData || !Array.isArray(jsonData)) { | |
throw new Error('Invalid data format received from API'); | |
} | |
const geojsonData = ConvertToGeojson(jsonData); | |
// ... rest of the code | |
mapRef.current.addSource('data', { | |
type: 'geojson', | |
data: geojsonData | |
}); | |
const popup = new mapboxgl.Popup({ | |
closeButton: false, | |
closeOnClick: false, | |
}); | |
// Listen for when the mouse enters a feature in your 'data-layer' | |
mapRef.current.on('mouseenter', 'data-layer', (e) => { | |
if (mapRef.current) { | |
mapRef.current.getCanvas().style.cursor = 'pointer'; | |
} | |
// Get the feature under the mouse pointer (the first feature). | |
const feature = e.features ? e.features[0] : null; | |
if (feature && feature.properties) { | |
// (Optional: Add any logic based on feature properties here) | |
} | |
if (!feature) return; | |
const htmlContent = ` | |
<div style="font-size: 14px; border-radius: 25px; background-color: white; padding: 10px; rgba(0, 0, 0, 0.2); max-width: full;"> | |
<div>${feature.properties?.time ?? 'Unknown time'}</div><br> | |
<div style="display: flex; justify-content: space-between; gap: 20px;"> | |
<strong style="display: flex;">${feature.properties?.location_name ?? 'Unknown Location'}</strong> | |
<div style="display: flex; font-weight: bold;">${feature.properties?.value.toFixed(2) ?? 'N/A'}µg/m³</div> | |
</div> | |
<div style="display: flex; gap: 10px;"> | |
<h1 style="font-weight: bold; color: ${feature.properties?.aqi_color ?? 'black'};">Air Quality is ${feature.properties?.aqi_category ?? 'N/A'}</h1> | |
<img src="${AirQuality.goodair}" style="background-color: ${feature.properties?.aqi_color ?? 'green'}; width: 30px; height: 30px; border-radius: 50%; font-size: 18px;"> | |
</div> | |
</div> | |
`; | |
// Set the popup at the feature's coordinates and add the HTML content. | |
popup | |
.setLngLat((feature.geometry as GeoJSON.Point).coordinates as [number, number]) | |
.setHTML(htmlContent); | |
if (mapRef.current) { | |
popup.addTo(mapRef.current); | |
} | |
}); | |
// When the mouse leaves the feature, remove the popup. | |
mapRef.current.on('mouseleave', 'data-layer', () => { | |
if (mapRef.current) { | |
mapRef.current.getCanvas().style.cursor = ''; | |
} | |
popup.remove(); | |
}); | |
Object.entries(AirQuality).forEach(([key, url]) => { | |
if (mapRef.current) { | |
mapRef.current.loadImage(url, (error, image) => { | |
if (error) throw error; | |
if (mapRef.current && image && !mapRef.current.hasImage(key)) { | |
mapRef.current.addImage(key, image); | |
} | |
}); | |
} | |
}); | |
mapRef.current.addLayer({ | |
'id': 'circle-layer', | |
'type': 'circle', | |
'source': 'data', | |
'paint': { | |
'circle-color': 'white', | |
'circle-radius': 30, | |
'circle-opacity': 1 | |
} | |
}); | |
mapRef.current.addLayer({ | |
'id': 'data-layer', | |
'type': 'symbol', | |
'source': 'data', | |
'layout': { | |
'icon-image': [ | |
'match', | |
['get', 'aqi_category'], | |
'Good', 'goodair', | |
'Moderate', 'moderate', | |
'Unhealthy', 'unhealthy', | |
'Unhealthy for Sensitive Groups', 'unhealthySG', | |
'Hazardous', 'Hazardous', | |
'Very Unhealthy', 'veryunhealthy', | |
'unknownAQ' | |
], | |
'icon-size': 0.05, | |
'icon-allow-overlap': true | |
}, | |
'filter': ['has', 'aqi_category'] | |
}); | |
} catch (error) { | |
console.error('Error fetching or processing map data:', error); | |
// TODO: Add user-friendly error handling | |
} |
🧰 Tools
🪛 Biome (1.9.4)
[error] 91-91: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Nitpick comments (2)
netmanager-app/components/NetManagerMap/page.tsx (2)
71-75
: Remove console.log statements and improve error handling.Remove debugging console.log statements and add proper error handling.
Apply this diff:
-const Data = await GetAirQuoData(AirQoToken?AirQoToken:"") -console.log(Data) -const geojsonData = ConvertToGeojson(Data); -console.log('GeoJson Data : ',geojsonData) +const Data = await GetAirQuoData(AirQoToken ?? ""); +if (!Data) { + throw new Error('Failed to fetch air quality data'); +} +const geojsonData = ConvertToGeojson(Data);
91-92
: Use optional chaining for better readability.The feature access can be simplified using optional chaining.
-const feature = e.features ? e.features[0] : null; +const feature = e.features?.[0];🧰 Tools
🪛 Biome (1.9.4)
[error] 92-92: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
netmanager-app/package-lock.json
is excluded by!**/package-lock.json
📒 Files selected for processing (3)
netmanager-app/api/MapData.ts
(1 hunks)netmanager-app/components/NetManagerMap/page.tsx
(1 hunks)netmanager-app/package.json
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- netmanager-app/package.json
🧰 Additional context used
🪛 Biome (1.9.4)
netmanager-app/api/MapData.ts
[error] 21-21: void is confusing inside a union type.
Unsafe fix: Use undefined instead.
(lint/suspicious/noConfusingVoidType)
[error] 10-13: This block statement doesn't serve any purpose and can be safely removed.
Standalone block statements without any block-level declarations are redundant in JavaScript and can be removed to simplify the code.
Safe fix: Remove redundant block.
(lint/complexity/noUselessLoneBlockStatements)
netmanager-app/components/NetManagerMap/page.tsx
[error] 92-92: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (1)
netmanager-app/components/NetManagerMap/page.tsx (1)
24-25
: Move sensitive tokens to environment variables.Good practice using environment variables for sensitive tokens.
netmanager-app/api/MapData.ts
Outdated
export const UserClick = async(access_token:string,sessionToken:string,locationid: string,)=>{ | ||
if (!access_token) { | ||
console.log("Missing Access Token"); | ||
return; | ||
} | ||
|
||
const response = await axios.get((`https://api.mapbox.com/search/searchbox/v1/retrieve/${locationid}?&access_token=${access_token}&session_token=${sessionToken}`)) | ||
const data = response.data; | ||
if(data){ | ||
return data; | ||
} | ||
|
||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add error handling and type safety.
The function needs several improvements:
- Add proper return type
- Add try-catch block
- Move API URL to environment variables
- Consistent error handling with other functions
Apply this diff to improve the implementation:
-export const UserClick = async(access_token:string,sessionToken:string,locationid: string,)=>{
+interface LocationData {
+ features: Array<{
+ geometry: {
+ coordinates: [number, number];
+ };
+ }>;
+}
+
+export const UserClick = async(
+ access_token: string,
+ sessionToken: string,
+ locationid: string
+): Promise<LocationData | undefined> => {
+ const API_URL = process.env.NEXT_PUBLIC_MAPBOX_API_URL;
+ if (!API_URL) {
+ console.error('API URL is not configured');
+ return undefined;
+ }
if (!access_token) {
- console.log("Missing Access Token");
- return;
+ console.error("Missing Access Token");
+ return undefined;
}
- const response = await axios.get((`https://api.mapbox.com/search/searchbox/v1/retrieve/${locationid}?&access_token=${access_token}&session_token=${sessionToken}`))
- const data = response.data;
- if(data){
- return data;
+ try {
+ const response = await axios.get(
+ `${API_URL}/search/searchbox/v1/retrieve/${locationid}?access_token=${access_token}&session_token=${sessionToken}`
+ );
+ return response.data;
+ } catch (error) {
+ console.error("Error retrieving location:", error);
+ return undefined;
}
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export const UserClick = async(access_token:string,sessionToken:string,locationid: string,)=>{ | |
if (!access_token) { | |
console.log("Missing Access Token"); | |
return; | |
} | |
const response = await axios.get((`https://api.mapbox.com/search/searchbox/v1/retrieve/${locationid}?&access_token=${access_token}&session_token=${sessionToken}`)) | |
const data = response.data; | |
if(data){ | |
return data; | |
} | |
} | |
interface LocationData { | |
features: Array<{ | |
geometry: { | |
coordinates: [number, number]; | |
}; | |
}>; | |
} | |
export const UserClick = async( | |
access_token: string, | |
sessionToken: string, | |
locationid: string | |
): Promise<LocationData | undefined> => { | |
const API_URL = process.env.NEXT_PUBLIC_MAPBOX_API_URL; | |
if (!API_URL) { | |
console.error('API URL is not configured'); | |
return undefined; | |
} | |
if (!access_token) { | |
console.error("Missing Access Token"); | |
return undefined; | |
} | |
try { | |
const response = await axios.get( | |
`${API_URL}/search/searchbox/v1/retrieve/${locationid}?access_token=${access_token}&session_token=${sessionToken}` | |
); | |
return response.data; | |
} catch (error) { | |
console.error("Error retrieving location:", error); | |
return undefined; | |
} | |
} |
netmanager-app/api/MapData.ts
Outdated
export const GetAirQuoData = async (token: string) =>{ | ||
try { | ||
if(token){ | ||
const response = await axios.get(`https://staging-analytics.airqo.net/api/v2/devices/readings/map?token=${token}`); | ||
const data = response.data; | ||
return data; | ||
} | ||
{ | ||
console.log("Error Token Is Missing") | ||
return null; | ||
} | ||
|
||
} | ||
catch (error) { | ||
console.error('Error fetching GeoJSON:', error); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance error handling and improve code structure.
The function has several areas for improvement:
- Remove the redundant block statement
- Add proper type for the return value
- Move the API URL to environment variables
- Strengthen token validation
Apply this diff to improve the implementation:
-export const GetAirQuoData = async (token: string) =>{
+export const GetAirQuoData = async (token: string): Promise<any | null> => {
+ const API_URL = process.env.NEXT_PUBLIC_AIRQO_API_URL;
+ if (!API_URL) {
+ console.error('API URL is not configured');
+ return null;
+ }
+ if (!token || typeof token !== 'string' || token.trim() === '') {
+ console.error('Invalid token provided');
+ return null;
+ }
try {
- if(token){
- const response = await axios.get(`https://staging-analytics.airqo.net/api/v2/devices/readings/map?token=${token}`);
- const data = response.data;
- return data;
- }
- {
- console.log("Error Token Is Missing")
- return null;
- }
+ const response = await axios.get(`${API_URL}/api/v2/devices/readings/map?token=${token}`);
+ return response.data;
}
catch (error) {
- console.error('Error fetching GeoJSON:', error);
+ console.error('Error fetching air quality data:', error);
+ return null;
}
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export const GetAirQuoData = async (token: string) =>{ | |
try { | |
if(token){ | |
const response = await axios.get(`https://staging-analytics.airqo.net/api/v2/devices/readings/map?token=${token}`); | |
const data = response.data; | |
return data; | |
} | |
{ | |
console.log("Error Token Is Missing") | |
return null; | |
} | |
} | |
catch (error) { | |
console.error('Error fetching GeoJSON:', error); | |
} | |
} | |
export const GetAirQuoData = async (token: string): Promise<any | null> => { | |
const API_URL = process.env.NEXT_PUBLIC_AIRQO_API_URL; | |
if (!API_URL) { | |
console.error('API URL is not configured'); | |
return null; | |
} | |
if (!token || typeof token !== 'string' || token.trim() === '') { | |
console.error('Invalid token provided'); | |
return null; | |
} | |
try { | |
const response = await axios.get(`${API_URL}/api/v2/devices/readings/map?token=${token}`); | |
return response.data; | |
} | |
catch (error) { | |
console.error('Error fetching air quality data:', error); | |
return null; | |
} | |
} |
🧰 Tools
🪛 Biome (1.9.4)
[error] 10-13: This block statement doesn't serve any purpose and can be safely removed.
Standalone block statements without any block-level declarations are redundant in JavaScript and can be removed to simplify the code.
Safe fix: Remove redundant block.
(lint/complexity/noUselessLoneBlockStatements)
netmanager-app/api/MapData.ts
Outdated
export const FetchSuggestions=async(value:string,access_token:string,sessionToken:string,latitude?: number, longitude?: number):Promise<any[] | void>=>{ | ||
|
||
if (!access_token) { | ||
console.log("Missing Access Token"); | ||
return; | ||
} | ||
|
||
if (latitude !== undefined && longitude !== undefined) { | ||
try{ | ||
const proximityParam = `${longitude},${latitude}`; | ||
const response = await axios.get(`https://api.mapbox.com/search/searchbox/v1/suggest?q=${value.toLowerCase()}&access_token=${access_token}&proximity=${proximityParam}&session_token=${sessionToken}`) | ||
const data = await response.data; | ||
|
||
console.log("data :",data.suggestions) | ||
if (data.suggestions) { | ||
return data.suggestions; | ||
}else{ | ||
console.log("Error fetching suggestions"); | ||
} | ||
|
||
} | ||
catch(error){ | ||
console.error("Error fetching suggestions:", error); | ||
} | ||
}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve type safety and reduce complexity.
The function needs several improvements:
- Replace
void
withundefined
in return type - Flatten the nested structure
- Consistent error handling
- Move API URL to environment variables
Apply this diff to improve the implementation:
-export const FetchSuggestions = async(value:string, access_token:string, sessionToken:string, latitude?: number, longitude?: number): Promise<any[] | void> => {
+interface MapboxSuggestion {
+ mapbox_id: string;
+ name: string;
+ place_formatted: string;
+}
+
+export const FetchSuggestions = async(
+ value: string,
+ access_token: string,
+ sessionToken: string,
+ latitude?: number,
+ longitude?: number
+): Promise<MapboxSuggestion[] | undefined> => {
+ const API_URL = process.env.NEXT_PUBLIC_MAPBOX_API_URL;
+ if (!API_URL) {
+ console.error('API URL is not configured');
+ return undefined;
+ }
if (!access_token) {
- console.log("Missing Access Token");
- return;
+ console.error("Missing Access Token");
+ return undefined;
}
- if (latitude !== undefined && longitude !== undefined) {
- try {
- const proximityParam = `${longitude},${latitude}`;
- const response = await axios.get(`https://api.mapbox.com/search/searchbox/v1/suggest?q=${value.toLowerCase()}&access_token=${access_token}&proximity=${proximityParam}&session_token=${sessionToken}`)
- const data = await response.data;
- console.log("data :", data.suggestions)
- if (data.suggestions) {
- return data.suggestions;
- } else {
- console.log("Error fetching suggestions");
- }
- } catch(error) {
- console.error("Error fetching suggestions:", error);
- }
+ try {
+ const proximityParam = latitude !== undefined && longitude !== undefined
+ ? `&proximity=${longitude},${latitude}`
+ : '';
+ const response = await axios.get(
+ `${API_URL}/search/searchbox/v1/suggest?q=${value.toLowerCase()}&access_token=${access_token}${proximityParam}&session_token=${sessionToken}`
+ );
+ return response.data.suggestions || [];
+ } catch(error) {
+ console.error("Error fetching suggestions:", error);
+ return undefined;
}
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export const FetchSuggestions=async(value:string,access_token:string,sessionToken:string,latitude?: number, longitude?: number):Promise<any[] | void>=>{ | |
if (!access_token) { | |
console.log("Missing Access Token"); | |
return; | |
} | |
if (latitude !== undefined && longitude !== undefined) { | |
try{ | |
const proximityParam = `${longitude},${latitude}`; | |
const response = await axios.get(`https://api.mapbox.com/search/searchbox/v1/suggest?q=${value.toLowerCase()}&access_token=${access_token}&proximity=${proximityParam}&session_token=${sessionToken}`) | |
const data = await response.data; | |
console.log("data :",data.suggestions) | |
if (data.suggestions) { | |
return data.suggestions; | |
}else{ | |
console.log("Error fetching suggestions"); | |
} | |
} | |
catch(error){ | |
console.error("Error fetching suggestions:", error); | |
} | |
}} | |
interface MapboxSuggestion { | |
mapbox_id: string; | |
name: string; | |
place_formatted: string; | |
} | |
export const FetchSuggestions = async( | |
value: string, | |
access_token: string, | |
sessionToken: string, | |
latitude?: number, | |
longitude?: number | |
): Promise<MapboxSuggestion[] | undefined> => { | |
const API_URL = process.env.NEXT_PUBLIC_MAPBOX_API_URL; | |
if (!API_URL) { | |
console.error('API URL is not configured'); | |
return undefined; | |
} | |
if (!access_token) { | |
console.error("Missing Access Token"); | |
return undefined; | |
} | |
try { | |
const proximityParam = | |
latitude !== undefined && longitude !== undefined | |
? `&proximity=${longitude},${latitude}` | |
: ''; | |
const response = await axios.get( | |
`${API_URL}/search/searchbox/v1/suggest?q=${value.toLowerCase()}&access_token=${access_token}${proximityParam}&session_token=${sessionToken}` | |
); | |
return response.data.suggestions || []; | |
} catch (error) { | |
console.error("Error fetching suggestions:", error); | |
return undefined; | |
} | |
}; |
🧰 Tools
🪛 Biome (1.9.4)
[error] 21-21: void is confusing inside a union type.
Unsafe fix: Use undefined instead.
(lint/suspicious/noConfusingVoidType)
onClick={() => setIsOpen(true)} | ||
title='Refresh Map' | ||
icon={<RefreshIcon width={24} height={24} fill={""} />} | ||
/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix incorrect onClick handler.
The refresh button uses the same onClick handler as the layer button.
-onClick={() => setIsOpen(true)}
+onClick={() => {
+ // TODO: Implement refresh functionality
+ // Uncomment when ready:
+ // refreshMap();
+}}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
onClick={() => setIsOpen(true)} | |
title='Refresh Map' | |
icon={<RefreshIcon width={24} height={24} fill={""} />} | |
/> | |
onClick={() => { | |
// TODO: Implement refresh functionality | |
// Uncomment when ready: | |
// refreshMap(); | |
}} | |
title='Refresh Map' | |
icon={<RefreshIcon width={24} height={24} fill={""} />} | |
/> |
<IconButton | ||
onClick={() => setIsOpen(true)} | ||
title='Refresh Map' | ||
icon={<ShareIcon width={24} height={24} fill={""} />} | ||
/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix incorrect title and onClick handler for share button.
The share button has incorrect title and reuses layer button's onClick handler.
-onClick={() => setIsOpen(true)}
-title='Refresh Map'
+onClick={() => {
+ // TODO: Implement share functionality
+}}
+title='Share Map'
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
<IconButton | |
onClick={() => setIsOpen(true)} | |
title='Refresh Map' | |
icon={<ShareIcon width={24} height={24} fill={""} />} | |
/> | |
<IconButton | |
onClick={() => { | |
// TODO: Implement share functionality | |
}} | |
title='Share Map' | |
icon={<ShareIcon width={24} height={24} fill={""} />} | |
/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
♻️ Duplicate comments (1)
netmanager-app/components/NetManagerMap/page.tsx (1)
311-329
:⚠️ Potential issueFix incorrect button handlers and titles.
The buttons have incorrect onClick handlers and titles.
<IconButton onClick={() => setIsOpen(true)} title='Map Layers' icon={<LayerIcon width={24} height={24} fill={""}/>} /> <IconButton - onClick={() => setIsOpen(true)} + onClick={() => { + // TODO: Implement refresh functionality + // refreshMap(); + }} title='Refresh Map' icon={<RefreshIcon width={24} height={24} fill={""} />} /> <IconButton - onClick={() => setIsOpen(true)} - title='Refresh Map' + onClick={() => { + // TODO: Implement share functionality + }} + title='Share Map' icon={<ShareIcon width={24} height={24} fill={""} />} />
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
netmanager-app/components/NetManagerMap/page.tsx
(1 hunks)netmanager-app/core/apis/MapData.ts
(1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
netmanager-app/components/NetManagerMap/page.tsx
[error] 92-92: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
netmanager-app/core/apis/MapData.ts
[error] 21-21: void is confusing inside a union type.
Unsafe fix: Use undefined instead.
(lint/suspicious/noConfusingVoidType)
[error] 10-13: This block statement doesn't serve any purpose and can be safely removed.
Standalone block statements without any block-level declarations are redundant in JavaScript and can be removed to simplify the code.
Safe fix: Remove redundant block.
(lint/complexity/noUselessLoneBlockStatements)
🔇 Additional comments (1)
netmanager-app/components/NetManagerMap/page.tsx (1)
182-231
: Optimize search functionality with debouncing.The search functionality needs optimization and better type safety.
+const DEBOUNCE_DELAY = 300; + +const useDebounce = <T>(value: T, delay: number): T => { + const [debouncedValue, setDebouncedValue] = useState<T>(value); + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + return () => { + clearTimeout(handler); + }; + }, [value, delay]); + + return debouncedValue; +}; + -const SearchSuggestions=(e: React.ChangeEvent<HTMLInputElement>)=>{ +const handleSearchInput = (e: React.ChangeEvent<HTMLInputElement>) => { const value = e.target.value; setQuery(value); +}; + +const debouncedQuery = useDebounce(query, DEBOUNCE_DELAY); + +useEffect(() => { if (value.trim() === "") { setSuggestions([]); // ... rest of the code } fetchUserLocation(); -} +}, [debouncedQuery]);
netmanager-app/core/apis/MapData.ts
Outdated
export const GetAirQuoData = async (token: string) =>{ | ||
try { | ||
if(token){ | ||
const response = await axios.get(`https://staging-analytics.airqo.net/api/v2/devices/readings/map?token=${token}`); | ||
const data = response.data; | ||
return data; | ||
} | ||
{ | ||
console.log("Error Token Is Missing") | ||
return null; | ||
} | ||
|
||
} | ||
catch (error) { | ||
console.error('Error fetching GeoJSON:', error); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance error handling and type safety in GetAirQuoData.
The function needs improvements in error handling, type safety, and configuration management.
+interface AirQoResponse {
+ // TODO: Add specific types based on actual response structure
+ [key: string]: any;
+}
+
-export const GetAirQuoData = async (token: string) =>{
+export const GetAirQuoData = async (token: string): Promise<AirQoResponse | null> => {
try {
if(token){
- const response = await axios.get(`https://staging-analytics.airqo.net/api/v2/devices/readings/map?token=${token}`);
- const data = response.data;
- return data;
+ const baseUrl = process.env.NEXT_PUBLIC_AIRQO_API_URL || 'https://staging-analytics.airqo.net';
+ const response = await axios.get<AirQoResponse>(`${baseUrl}/api/v2/devices/readings/map?token=${token}`);
+ return response.data;
}
- {
- console.log("Error Token Is Missing")
- return null;
- }
+ console.error("Error: Token is missing");
+ return null;
}
catch (error) {
- console.error('Error fetching GeoJSON:', error);
+ console.error('Error fetching air quality data:', error);
+ throw new Error('Failed to fetch air quality data');
}
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export const GetAirQuoData = async (token: string) =>{ | |
try { | |
if(token){ | |
const response = await axios.get(`https://staging-analytics.airqo.net/api/v2/devices/readings/map?token=${token}`); | |
const data = response.data; | |
return data; | |
} | |
{ | |
console.log("Error Token Is Missing") | |
return null; | |
} | |
} | |
catch (error) { | |
console.error('Error fetching GeoJSON:', error); | |
} | |
} | |
interface AirQoResponse { | |
// TODO: Add specific types based on the actual response structure | |
[key: string]: any; | |
} | |
export const GetAirQuoData = async (token: string): Promise<AirQoResponse | null> => { | |
try { | |
if (token) { | |
const baseUrl = process.env.NEXT_PUBLIC_AIRQO_API_URL || 'https://staging-analytics.airqo.net'; | |
const response = await axios.get<AirQoResponse>(`${baseUrl}/api/v2/devices/readings/map?token=${token}`); | |
return response.data; | |
} | |
console.error("Error: Token is missing"); | |
return null; | |
} | |
catch (error) { | |
console.error('Error fetching air quality data:', error); | |
throw new Error('Failed to fetch air quality data'); | |
} | |
} |
🧰 Tools
🪛 Biome (1.9.4)
[error] 10-13: This block statement doesn't serve any purpose and can be safely removed.
Standalone block statements without any block-level declarations are redundant in JavaScript and can be removed to simplify the code.
Safe fix: Remove redundant block.
(lint/complexity/noUselessLoneBlockStatements)
netmanager-app/core/apis/MapData.ts
Outdated
export const FetchSuggestions=async(value:string,access_token:string,sessionToken:string,latitude?: number, longitude?: number):Promise<any[] | void>=>{ | ||
|
||
if (!access_token) { | ||
console.log("Missing Access Token"); | ||
return; | ||
} | ||
|
||
if (latitude !== undefined && longitude !== undefined) { | ||
try{ | ||
const proximityParam = `${longitude},${latitude}`; | ||
const response = await axios.get(`https://api.mapbox.com/search/searchbox/v1/suggest?q=${value.toLowerCase()}&access_token=${access_token}&proximity=${proximityParam}&session_token=${sessionToken}`) | ||
const data = await response.data; | ||
|
||
console.log("data :",data.suggestions) | ||
if (data.suggestions) { | ||
return data.suggestions; | ||
}else{ | ||
console.log("Error fetching suggestions"); | ||
} | ||
|
||
} | ||
catch(error){ | ||
console.error("Error fetching suggestions:", error); | ||
} | ||
}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve type safety and error handling in FetchSuggestions.
The function needs better type definitions and consistent error handling.
+interface MapboxSuggestion {
+ mapbox_id: string;
+ name: string;
+ place_formatted: string;
+}
+
+interface MapboxResponse {
+ suggestions: MapboxSuggestion[];
+}
+
-export const FetchSuggestions = async(value:string,access_token:string,sessionToken:string,latitude?: number, longitude?: number):Promise<any[] | void> => {
+export const FetchSuggestions = async(
+ value: string,
+ access_token: string,
+ sessionToken: string,
+ latitude?: number,
+ longitude?: number
+): Promise<MapboxSuggestion[] | null> => {
if (!access_token) {
- console.log("Missing Access Token");
- return;
+ throw new Error("Missing access token");
}
+ try {
+ const params = new URLSearchParams({
+ q: value.toLowerCase(),
+ access_token,
+ session_token: sessionToken,
+ ...(latitude !== undefined && longitude !== undefined && {
+ proximity: `${longitude},${latitude}`
+ })
+ });
+
+ const response = await axios.get<MapboxResponse>(
+ `https://api.mapbox.com/search/searchbox/v1/suggest?${params}`
+ );
+
+ return response.data.suggestions || null;
+ } catch (error) {
+ console.error("Error fetching suggestions:", error);
+ throw new Error("Failed to fetch location suggestions");
+ }
- if (latitude !== undefined && longitude !== undefined) {
- try{
- const proximityParam = `${longitude},${latitude}`;
- const response = await axios.get(`https://api.mapbox.com/search/searchbox/v1/suggest?q=${value.toLowerCase()}&access_token=${access_token}&proximity=${proximityParam}&session_token=${sessionToken}`)
- const data = await response.data;
-
- console.log("data :",data.suggestions)
- if (data.suggestions) {
- return data.suggestions;
- }else{
- console.log("Error fetching suggestions");
- }
-
- }
- catch(error){
- console.error("Error fetching suggestions:", error);
- }
- }}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export const FetchSuggestions=async(value:string,access_token:string,sessionToken:string,latitude?: number, longitude?: number):Promise<any[] | void>=>{ | |
if (!access_token) { | |
console.log("Missing Access Token"); | |
return; | |
} | |
if (latitude !== undefined && longitude !== undefined) { | |
try{ | |
const proximityParam = `${longitude},${latitude}`; | |
const response = await axios.get(`https://api.mapbox.com/search/searchbox/v1/suggest?q=${value.toLowerCase()}&access_token=${access_token}&proximity=${proximityParam}&session_token=${sessionToken}`) | |
const data = await response.data; | |
console.log("data :",data.suggestions) | |
if (data.suggestions) { | |
return data.suggestions; | |
}else{ | |
console.log("Error fetching suggestions"); | |
} | |
} | |
catch(error){ | |
console.error("Error fetching suggestions:", error); | |
} | |
}} | |
interface MapboxSuggestion { | |
mapbox_id: string; | |
name: string; | |
place_formatted: string; | |
} | |
interface MapboxResponse { | |
suggestions: MapboxSuggestion[]; | |
} | |
export const FetchSuggestions = async( | |
value: string, | |
access_token: string, | |
sessionToken: string, | |
latitude?: number, | |
longitude?: number | |
): Promise<MapboxSuggestion[] | null> => { | |
if (!access_token) { | |
throw new Error("Missing access token"); | |
} | |
try { | |
const params = new URLSearchParams({ | |
q: value.toLowerCase(), | |
access_token, | |
session_token: sessionToken, | |
...(latitude !== undefined && | |
longitude !== undefined && { | |
proximity: `${longitude},${latitude}` | |
}) | |
}); | |
const response = await axios.get<MapboxResponse>( | |
`https://api.mapbox.com/search/searchbox/v1/suggest?${params}` | |
); | |
return response.data.suggestions || null; | |
} catch (error) { | |
console.error("Error fetching suggestions:", error); | |
throw new Error("Failed to fetch location suggestions"); | |
} | |
}; |
🧰 Tools
🪛 Biome (1.9.4)
[error] 21-21: void is confusing inside a union type.
Unsafe fix: Use undefined instead.
(lint/suspicious/noConfusingVoidType)
netmanager-app/core/apis/MapData.ts
Outdated
export const UserClick = async(access_token:string,sessionToken:string,locationid: string,)=>{ | ||
if (!access_token) { | ||
console.log("Missing Access Token"); | ||
return; | ||
} | ||
|
||
const response = await axios.get((`https://api.mapbox.com/search/searchbox/v1/retrieve/${locationid}?&access_token=${access_token}&session_token=${sessionToken}`)) | ||
const data = response.data; | ||
if(data){ | ||
return data; | ||
} | ||
|
||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add type safety and error handling to UserClick.
The function needs proper type definitions and consistent error handling.
+interface MapboxLocation {
+ features: Array<{
+ geometry: {
+ coordinates: [number, number];
+ };
+ }>;
+}
+
-export const UserClick = async(access_token:string,sessionToken:string,locationid: string,)=>{
+export const UserClick = async(
+ access_token: string,
+ sessionToken: string,
+ locationid: string
+): Promise<MapboxLocation> => {
if (!access_token) {
- console.log("Missing Access Token");
- return;
+ throw new Error("Missing access token");
}
- const response = await axios.get((`https://api.mapbox.com/search/searchbox/v1/retrieve/${locationid}?&access_token=${access_token}&session_token=${sessionToken}`))
- const data = response.data;
- if(data){
- return data;
- }
+ try {
+ const response = await axios.get<MapboxLocation>(
+ `https://api.mapbox.com/search/searchbox/v1/retrieve/${locationid}`,
+ {
+ params: {
+ access_token,
+ session_token: sessionToken
+ }
+ }
+ );
+ return response.data;
+ } catch (error) {
+ console.error("Error fetching location:", error);
+ throw new Error("Failed to fetch location details");
+ }
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export const UserClick = async(access_token:string,sessionToken:string,locationid: string,)=>{ | |
if (!access_token) { | |
console.log("Missing Access Token"); | |
return; | |
} | |
const response = await axios.get((`https://api.mapbox.com/search/searchbox/v1/retrieve/${locationid}?&access_token=${access_token}&session_token=${sessionToken}`)) | |
const data = response.data; | |
if(data){ | |
return data; | |
} | |
} | |
interface MapboxLocation { | |
features: Array<{ | |
geometry: { | |
coordinates: [number, number]; | |
}; | |
}>; | |
} | |
export const UserClick = async( | |
access_token: string, | |
sessionToken: string, | |
locationid: string | |
): Promise<MapboxLocation> => { | |
if (!access_token) { | |
throw new Error("Missing access token"); | |
} | |
try { | |
const response = await axios.get<MapboxLocation>( | |
`https://api.mapbox.com/search/searchbox/v1/retrieve/${locationid}`, | |
{ | |
params: { | |
access_token, | |
session_token: sessionToken | |
} | |
} | |
); | |
return response.data; | |
} catch (error) { | |
console.error("Error fetching location:", error); | |
throw new Error("Failed to fetch location details"); | |
} | |
} |
// const refreshMap = useRefreshMap( | ||
// setToastMessage, | ||
// mapRef, | ||
// dispatch, | ||
// selectedNode, | ||
// ); | ||
//Get the Session Token for the User |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
❓ Verification inconclusive
Remove commented code or implement refresh functionality.
The commented refreshMap code should either be implemented or removed.
🏁 Script executed:
#!/bin/bash
# Search for useRefreshMap implementation
ast-grep --pattern 'function useRefreshMap($$$) {
$$$
}'
Length of output: 60
Action Required: Remove Unimplemented Refresh Code
After verifying the repository, no implementation for the useRefreshMap
hook was found. Please either remove the commented-out refreshMap
code in netmanager-app/components/NetManagerMap/page.tsx
(lines 43-49) or implement the intended refresh functionality if required.
closeButton: false, | ||
closeOnClick: false, | ||
|
||
}); | ||
// Listen for when the mouse enters a feature in your 'data-layer' | ||
mapRef.current.on('mouseenter', 'data-layer', (e) => { | ||
if (mapRef.current) { | ||
mapRef.current.getCanvas().style.cursor = 'pointer'; | ||
} | ||
// Get the feature under the mouse pointer (the first feature). | ||
const feature = e.features ? e.features[0] : null; | ||
if (feature && feature.properties) { | ||
|
||
} | ||
|
||
if (!feature) return; | ||
|
||
const htmlContent = ` | ||
<div style="font-size: 14px; border-radius: 25px; background-color: white; padding: 10px; rgba(0, 0, 0, 0.2); max-width: full;"> | ||
<div>${feature.properties?.time ?? 'Unknown time'}</div><br> | ||
<div style="display: flex; justify-content: space-between;gap: 20px;"> | ||
<strong style="display: flex; ">${feature.properties?.location_name ?? 'Unknown Location'}</strong> | ||
<div style="display: flex;font-weight: bold; ">${feature.properties?.value.toFixed(2) ?? 'N/A'}µg/m³</div> | ||
</div> | ||
|
||
<div style=" display: flex;gap: 10px; "> | ||
<h1 style="font-weight: bold; color: ${feature.properties?.aqi_color ?? 'black'};">Air Quality is ${feature.properties?.aqi_category ?? 'N/A'}</h1> | ||
<img src="${AirQuality.goodair}" style="background-color: ${feature.properties?.aqi_color ?? 'green'};width: 30px; height: 30px;border-radius: 50%;font-size: 18px;"></img> | ||
</div> | ||
</div> | ||
`; | ||
|
||
// Set the popup at the feature's coordinates and add the HTML content. | ||
popup | ||
.setLngLat((feature.geometry as GeoJSON.Point).coordinates as [number, number]) | ||
.setHTML(htmlContent) | ||
if (mapRef.current) { | ||
popup.addTo(mapRef.current); | ||
} | ||
}); | ||
|
||
// When the mouse leaves the feature, remove the popup. | ||
mapRef.current.on('mouseleave', 'data-layer', () => { | ||
if (mapRef.current) { | ||
mapRef.current.getCanvas().style.cursor = ''; | ||
} | ||
popup.remove(); | ||
}); | ||
Object.entries(AirQuality).forEach(([key, url]) => { | ||
if (mapRef.current) { | ||
mapRef.current.loadImage(url, (error, image) => { | ||
if (error) throw error; | ||
|
||
if (mapRef.current && image && !mapRef.current.hasImage(key)) { | ||
mapRef.current.addImage(key, image); | ||
} | ||
}); | ||
} | ||
}); | ||
mapRef.current.addLayer({ | ||
'id': 'circle-layer', | ||
'type': 'circle', | ||
'source': 'data', | ||
'paint': { | ||
'circle-color': 'white', | ||
'circle-radius': 30, | ||
'circle-opacity': 1 | ||
} | ||
}); | ||
mapRef.current.addLayer({ | ||
'id': 'data-layer', | ||
'type': 'symbol', | ||
'source': 'data', | ||
'layout': { | ||
'icon-image': [ | ||
'match', | ||
['get', 'aqi_category'], | ||
'Good', 'goodair', | ||
'Moderate', 'moderate', | ||
'Unhealthy', 'unhealthy', | ||
'Unhealthy for Sensitive Groups', 'unhealthySG', | ||
'Hazardous','Hazardous', | ||
'Very Unhealthy', 'veryunhealthy', | ||
'unknownAQ' | ||
], | ||
'icon-size': 0.05, | ||
'icon-allow-overlap': true | ||
}, | ||
|
||
'filter': ['has', 'aqi_category'] | ||
}); | ||
|
||
|
||
} catch (error) { | ||
console.error('Error fetching GeoJSON:', error); | ||
} | ||
} | ||
}); | ||
} | ||
}, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Refactor map initialization for better maintainability.
The map initialization logic is complex and needs to be broken down into smaller functions.
+const initializeMap = (container: HTMLDivElement, token: string) => {
+ return new mapboxgl.Map({
+ container,
+ style: 'mapbox://styles/mapbox/streets-v11',
+ center: [18.5, 3],
+ zoom: 3
+ });
+};
+
+const setupMapLayers = async (map: mapboxgl.Map, airQoToken: string) => {
+ try {
+ const data = await GetAirQuoData(airQoToken);
+ if (!data) return;
+
+ const geojsonData = ConvertToGeojson(data);
+ map.addSource('data', {
+ type: 'geojson',
+ data: geojsonData
+ });
+
+ // Add layers here...
+ } catch (error) {
+ console.error('Error setting up map layers:', error);
+ // TODO: Show user-friendly error message
+ }
+};
+
useEffect(() => {
mapboxgl.accessToken = token;
if (mapContainerRef.current) {
- mapRef.current = new mapboxgl.Map({
- container: mapContainerRef.current,
- style:'mapbox://styles/mapbox/streets-v11',
- center: [18.5, 3],
- zoom: 3
- });
+ mapRef.current = initializeMap(mapContainerRef.current, token);
mapRef.current.on('load', async () => {
if (mapRef.current) {
- try {
- const Data = await GetAirQuoData(AirQoToken?AirQoToken:"")
- console.log(Data)
- // ... rest of the code
- } catch (error) {
- console.error('Error fetching GeoJSON:', error);
- }
+ await setupMapLayers(mapRef.current, AirQoToken ?? "");
}
});
}
}, []);
🧰 Tools
🪛 Biome (1.9.4)
[error] 92-92: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
const NetManagerMap = () => { | ||
// const { width } = useWindowSize(); | ||
const mapContainerRef = useRef<HTMLDivElement>(null); | ||
const mapRef = useRef<mapboxgl.Map | null>(null); | ||
const [query, setQuery] = useState(""); | ||
const [locationId,setlocationId] =useState("") | ||
const [suggestions, setSuggestions] = useState<any[]>([]); | ||
const [sessionToken, setSessionToken] = useState<string | null>(null); | ||
const token = process.env.NEXT_PUBLIC_MAP_API_TOKEN | ||
const AirQoToken = process.env.NEXT_PUBLIC_AIRQO_DATA_TOKEN | ||
const [isOpen, setIsOpen] = useState(false); | ||
const [NodeType, setNodeType] = useState('Emoji'); | ||
const [mapStyle, setMapStyle] = useState( | ||
'mapbox://styles/mapbox/streets-v11', | ||
); | ||
|
||
const AirQuality= { | ||
goodair :'/images/map/GoodAir.png', | ||
moderate :'/images/map/Moderate.png', | ||
hazardous :'/images/map/Hazardous.png', | ||
unhealthy: '/images/map/Unhealthy.png', | ||
veryunhealthy :'/images/map/VeryUnhealthy.png', | ||
unknownAQ:'/images/map/VeryUnhealthy.png', | ||
unhealthySG: '/images/map/UnhealthySG.png', | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add TypeScript interfaces and move constants to configuration.
The component needs better type safety and configuration management.
+interface AirQualityConfig {
+ [key: string]: string;
+}
+
+interface MapState {
+ query: string;
+ locationId: string;
+ suggestions: any[]; // TODO: Replace with proper type from MapData.ts
+ sessionToken: string | null;
+ isOpen: boolean;
+ nodeType: string;
+ mapStyle: string;
+}
+
const NetManagerMap = () => {
- const mapContainerRef = useRef<HTMLDivElement>(null);
- const mapRef = useRef<mapboxgl.Map | null>(null);
- const [query, setQuery] = useState("");
- const [locationId,setlocationId] =useState("")
- const [suggestions, setSuggestions] = useState<any[]>([]);
- const [sessionToken, setSessionToken] = useState<string | null>(null);
- const token = process.env.NEXT_PUBLIC_MAP_API_TOKEN
- const AirQoToken = process.env.NEXT_PUBLIC_AIRQO_DATA_TOKEN
- const [isOpen, setIsOpen] = useState(false);
- const [NodeType, setNodeType] = useState('Emoji');
- const [mapStyle, setMapStyle] = useState(
- 'mapbox://styles/mapbox/streets-v11',
- );
+ const mapContainerRef = useRef<HTMLDivElement>(null);
+ const mapRef = useRef<mapboxgl.Map | null>(null);
+
+ const [state, setState] = useState<MapState>({
+ query: "",
+ locationId: "",
+ suggestions: [],
+ sessionToken: null,
+ isOpen: false,
+ nodeType: 'Emoji',
+ mapStyle: 'mapbox://styles/mapbox/streets-v11'
+ });
+
+ const token = process.env.NEXT_PUBLIC_MAP_API_TOKEN;
+ const AirQoToken = process.env.NEXT_PUBLIC_AIRQO_DATA_TOKEN;
- const AirQuality= {
- goodair :'/images/map/GoodAir.png',
- moderate :'/images/map/Moderate.png',
- hazardous :'/images/map/Hazardous.png',
- unhealthy: '/images/map/Unhealthy.png',
- veryunhealthy :'/images/map/VeryUnhealthy.png',
- unknownAQ:'/images/map/VeryUnhealthy.png',
- unhealthySG: '/images/map/UnhealthySG.png',
- }
+ const AirQuality: AirQualityConfig = {
+ goodair: '/images/map/GoodAir.png',
+ moderate: '/images/map/Moderate.png',
+ hazardous: '/images/map/Hazardous.png',
+ unhealthy: '/images/map/Unhealthy.png',
+ veryunhealthy: '/images/map/VeryUnhealthy.png',
+ unknownAQ: '/images/map/VeryUnhealthy.png',
+ unhealthySG: '/images/map/UnhealthySG.png',
+ };
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const NetManagerMap = () => { | |
// const { width } = useWindowSize(); | |
const mapContainerRef = useRef<HTMLDivElement>(null); | |
const mapRef = useRef<mapboxgl.Map | null>(null); | |
const [query, setQuery] = useState(""); | |
const [locationId,setlocationId] =useState("") | |
const [suggestions, setSuggestions] = useState<any[]>([]); | |
const [sessionToken, setSessionToken] = useState<string | null>(null); | |
const token = process.env.NEXT_PUBLIC_MAP_API_TOKEN | |
const AirQoToken = process.env.NEXT_PUBLIC_AIRQO_DATA_TOKEN | |
const [isOpen, setIsOpen] = useState(false); | |
const [NodeType, setNodeType] = useState('Emoji'); | |
const [mapStyle, setMapStyle] = useState( | |
'mapbox://styles/mapbox/streets-v11', | |
); | |
const AirQuality= { | |
goodair :'/images/map/GoodAir.png', | |
moderate :'/images/map/Moderate.png', | |
hazardous :'/images/map/Hazardous.png', | |
unhealthy: '/images/map/Unhealthy.png', | |
veryunhealthy :'/images/map/VeryUnhealthy.png', | |
unknownAQ:'/images/map/VeryUnhealthy.png', | |
unhealthySG: '/images/map/UnhealthySG.png', | |
} | |
interface AirQualityConfig { | |
[key: string]: string; | |
} | |
interface MapState { | |
query: string; | |
locationId: string; | |
suggestions: any[]; // TODO: Replace with proper type from MapData.ts | |
sessionToken: string | null; | |
isOpen: boolean; | |
nodeType: string; | |
mapStyle: string; | |
} | |
const NetManagerMap = () => { | |
const mapContainerRef = useRef<HTMLDivElement>(null); | |
const mapRef = useRef<mapboxgl.Map | null>(null); | |
const [state, setState] = useState<MapState>({ | |
query: "", | |
locationId: "", | |
suggestions: [], | |
sessionToken: null, | |
isOpen: false, | |
nodeType: 'Emoji', | |
mapStyle: 'mapbox://styles/mapbox/streets-v11' | |
}); | |
const token = process.env.NEXT_PUBLIC_MAP_API_TOKEN; | |
const AirQoToken = process.env.NEXT_PUBLIC_AIRQO_DATA_TOKEN; | |
const AirQuality: AirQualityConfig = { | |
goodair: '/images/map/GoodAir.png', | |
moderate: '/images/map/Moderate.png', | |
hazardous: '/images/map/Hazardous.png', | |
unhealthy: '/images/map/Unhealthy.png', | |
veryunhealthy: '/images/map/VeryUnhealthy.png', | |
unknownAQ: '/images/map/VeryUnhealthy.png', | |
unhealthySG: '/images/map/UnhealthySG.png', | |
}; | |
// ... rest of the component code | |
}; |
7bc07c7
to
df21e0a
Compare
Summary of Changes (What does this PR do?)
Status of maturity (all need to be checked before merging):
How should this be manually tested?
npm install mapbox-gl
npm run dev
Summary by CodeRabbit