Skip to content
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

Add Leaflet map component for OSBL location visualization #198

Merged
merged 4 commits into from
Feb 21, 2025

Conversation

mpressen
Copy link
Contributor

@mpressen mpressen commented Feb 20, 2025

  • Integrate Leaflet and react-leaflet for interactive location mapping
  • Create LocationTab component to display OSBL locations on a map
  • Add custom marker styles and location type differentiation
  • Implement location selection and hover interactions
  • Update package.json with Leaflet and related type dependencies

Summary by CodeRabbit

  • New Features

    • Introduced an interactive location view featuring a map with markers alongside a list of locations for easy navigation.
    • Added a new component for displaying detailed information about each location.
    • Implemented a custom hook to manage interactions with location markers on the map.
  • Enhancements

    • Improved responsiveness and layout across various screens, including refined spacing and padding.
    • Enhanced data displays with clear fallback values for missing information, ensuring a smoother user experience.
  • Style

    • Revamped marker visuals for a more dynamic and engaging map presentation.

- Integrate Leaflet and react-leaflet for interactive location mapping
- Create LocationTab component to display OSBL locations on a map
- Add custom marker styles and location type differentiation
- Implement location selection and hover interactions
- Update package.json with Leaflet and related type dependencies
@mpressen mpressen linked an issue Feb 20, 2025 that may be closed by this pull request
Copy link
Contributor

coderabbitai bot commented Feb 20, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

This update introduces a new React component (LocationTab) that displays an interactive map and a corresponding list of locations using the react-leaflet library. It enables users to select and hover over locations with synchronized map and list interactions. Additionally, a custom debouncing hook (useDebounce) has been added, and data handling logic in getOsblData has been refined for better type consistency and attribute preservation. Minor interface adjustments, layout updates in multiple components, and new CSS classes for custom marker styling were also implemented, along with new dependencies for Leaflet integration.

Changes

File(s) Change Summary
app/frontend/components/pages/osbl/show/LocationTab.tsx
app/frontend/pages/Osbl/Show.tsx
Introduces the LocationTab component to render a map with markers and an interactive location list. Updates the osbl page to replace missing information with the new component.
app/frontend/hooks/useDebounce.ts Adds a custom debouncing hook that returns a delayed callback execution function, clearing timeouts as needed.
app/frontend/lib/osblData.ts Updates the getOsblData function to preserve the city value if present and casts latitude and longitude to number.
app/frontend/pages/Contribution/types.ts Modifies the OsblUpdate interface by adding a required city field to locations_attributes and changes latitude and longitude from number to string.
app/frontend/components/pages/osbl/show/DataSheetTab.tsx
app/frontend/components/pages/osbl/show/GestionTab.tsx
Adjusts layout and styling: alters padding and grid gaps in DataSheetTab; updates GestionTab to use nullish coalescing for displaying finance.year.
package.json Adds new dependencies: @types/leaflet, leaflet, and react-leaflet to support map functionality.
app/frontend/entrypoints/application.css Introduces new CSS classes for marker elements including base, pin, variant, and active styles, enhancing marker visualization and interactivity.
app/frontend/components/pages/osbl/show/LocationTab/LocationsList.tsx Adds a new LocationsList component to display a list of locations with selection and hover capabilities.
app/frontend/components/pages/osbl/show/LocationTab/LocationsList/LocationItemContent.tsx Introduces LocationItemContent component for displaying detailed information about a location.
app/frontend/components/pages/osbl/show/LocationTab/OsblMap.tsx Adds OsblMap component to render a map with markers using react-leaflet.
app/frontend/components/pages/osbl/show/LocationTab/useLocationInteractions.ts Introduces useLocationInteractions hook for managing interactions with location markers on the map.
app/frontend/lib/utils.ts Adds a new utility function capitalize for string manipulation.

Possibly related PRs

  • Osbl creation fourth panel locations #153: The changes in the main PR, which introduce the LocationTab component and related functionalities for displaying locations, are directly related to the modifications in the retrieved PR that add locations_attributes to the osbl_params method, allowing for nested attributes related to locations.
  • Add Leaflet map component for OSBL location visualization #198: The changes in the main PR are directly related to those in the retrieved PR as both introduce the LocationTab component, which utilizes the leaflet library for rendering a map and managing location interactions.
  • Refactor and improve OSBL contribution and show page components #197: The changes in the main PR, specifically the introduction of the LocationTab component and its associated functions, are related to the modifications in the retrieved PR, which also introduces new components like DataSheetTab and GestionTab for the OSBL show page, indicating a shared context in enhancing the OSBL components.

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c54c0ab and 12804d8.

📒 Files selected for processing (4)
  • app/frontend/components/pages/osbl/show/LocationTab/LocationsList/LocationItemContent.tsx (1 hunks)
  • app/frontend/components/pages/osbl/show/LocationTab/OsblMap.tsx (1 hunks)
  • app/frontend/lib/osblData.ts (1 hunks)
  • app/frontend/pages/Osbl/Show.tsx (4 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

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?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (8)
app/frontend/hooks/useDebounce.ts (2)

3-6: Add input validation and JSDoc documentation.

The hook would benefit from parameter validation and comprehensive documentation.

+/**
+ * Creates a debounced version of a callback function that delays its execution.
+ * @param callback - The function to debounce
+ * @param delay - The delay in milliseconds (must be positive)
+ * @returns A debounced version of the callback
+ * @throws {Error} If delay is not a positive number
+ */
 export function useDebounce<T extends (...args: any[]) => void>(
   callback: T,
   delay: number
 ): T {
+  if (delay <= 0) {
+    throw new Error('Delay must be a positive number')
+  }

17-25: Optimize performance with useCallback.

The returned function is recreated on every render, which could cause unnecessary re-renders in child components.

+import { useEffect, useRef, useCallback } from 'react'

-  return ((...args: Parameters<T>) => {
+  return useCallback((...args: Parameters<T>) => {
     if (timeoutRef.current !== undefined) {
       window.clearTimeout(timeoutRef.current)
     }

     timeoutRef.current = window.setTimeout(() => {
       callback(...args)
     }, delay)
-  }) as T
+  }, [callback, delay]) as T
app/frontend/pages/Contribution/types.ts (1)

151-153: Consider using branded types for coordinates.

The change from number to string for coordinates could benefit from additional type safety using branded types.

+type Latitude = string & { readonly __brand: unique symbol }
+type Longitude = string & { readonly __brand: unique symbol }

 city: string
-latitude: string
-longitude: string
+latitude: Latitude
+longitude: Longitude
app/frontend/pages/Osbl/Show.tsx (1)

187-187: Add error boundary and loading state.

Consider wrapping the LocationTab with an error boundary and handling loading states for map initialization.

+import { Suspense, ErrorBoundary } from 'react'
+import { Spinner } from '@/components/ui/spinner'

-<LocationTab osbl={processedOsbl} />
+<ErrorBoundary fallback={<div>Error loading map</div>}>
+  <Suspense fallback={<Spinner />}>
+    <LocationTab osbl={processedOsbl} />
+  </Suspense>
+</ErrorBoundary>
app/frontend/components/pages/osbl/show/LocationTab.tsx (4)

63-63: Consider using a more specific type for the map state.

The map state should use the more specific LeafletMap type.

-  const [map, setMap] = useState<Map | null>(null)
+  const [map, setMap] = useState<LeafletMap | null>(null)

75-81: Consider memoizing the sort comparator function.

The sort comparator function is recreated on every render. Consider extracting and memoizing it.

+const sortLocationsComparator = (a: any, b: any) => {
+  if (a.type === 'Siège social') return -1
+  if (b.type === 'Siège social') return 1
+  return (a.name ?? a.type).localeCompare(b.name ?? b.type)
+}

const sortedLocations = useMemo(() => {
-  return [...(osbl.locations_attributes ?? [])].sort((a, b) => {
-    if (a.type === 'Siège social') return -1
-    if (b.type === 'Siège social') return 1
-    return (a.name ?? a.type).localeCompare(b.name ?? b.type)
-  })
+  return [...(osbl.locations_attributes ?? [])].sort(sortLocationsComparator)
}, [osbl.locations_attributes])

202-239: Consider adding error boundaries for map rendering.

The MapContainer component should be wrapped in an error boundary to gracefully handle rendering failures.

Would you like me to provide an implementation of a MapErrorBoundary component?


67-73: Consider adding a loading state for locations data.

The current implementation assumes locations data is immediately available. Consider adding a loading state.

+  if (osbl.locations_attributes === undefined) {
+    return (
+      <div className='text-center text-muted-foreground py-4'>
+        <span className='loading'>Chargement des localisations...</span>
+      </div>
+    )
+  }

  if (typeof osbl.locations_attributes?.length !== 'number' || osbl.locations_attributes.length === 0) {
    return (
      <div className='text-center text-muted-foreground py-4'>
        Aucune localisation n'a été renseignée.
      </div>
    )
  }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between caf280b and b5bf3e5.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (7)
  • app/frontend/components/pages/osbl/show/LocationTab.tsx (1 hunks)
  • app/frontend/hooks/useDebounce.ts (1 hunks)
  • app/frontend/lib/osblData.ts (1 hunks)
  • app/frontend/pages/Contribution/types.ts (1 hunks)
  • app/frontend/pages/Osbl/Show.tsx (2 hunks)
  • app/frontend/styles/markers.css (1 hunks)
  • package.json (2 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
app/frontend/components/pages/osbl/show/LocationTab.tsx

[error] 4-4: Do not shadow the global "Map" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: deploy
🔇 Additional comments (2)
package.json (1)

14-14:

✅ Verification successful

Verify package versions for security and compatibility.

Let's check if we're using the latest stable versions of these packages and ensure compatibility.

Also applies to: 62-62, 70-70


🌐 Web query:

What are the latest stable versions of leaflet and react-leaflet? Are there any known compatibility issues between them?

💡 Result:

The latest stable version of Leaflet is 1.9.4 (released May 18, 2023)[1][5], while React-Leaflet is at v5.0.0 (released December 14, 2024)[2][6].

Compatibility Notes:

  • React-Leaflet v5.x requires React v19 and Leaflet v1.8+[2][6].
  • Leaflet v1.9.x remains backward-compatible with plugins expecting Leaflet v1.x[5], so using it with React-Leaflet v5.x should work without issues.
  • Earlier React-Leaflet versions (e.g., v4.x) required Leaflet ≥1.8 and React ≥18[2][8], but these are no longer maintained.

Key Changes Impacting Compatibility:

Component Breaking Changes Details
Leaflet Maintenance mode for v1.x No new features; critical bug fixes only[5]. Future v2.x will drop IE support and adopt modern JS[5].
React-Leaflet Removed legacy APIs Dropped LeafletProvider, MapConsumer, and CommonJS/UMD builds[2][6]. Requires explicit ref usage instead of whenCreated[2].

For new projects, use:

<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>

Citations:


Package Versions Verified – No Action Required

  • Leaflet v1.9.4 is confirmed as the latest stable release.
  • React-Leaflet v5.0.0 is the current stable version, and its requirement of Leaflet v1.8+ is met.
  • There are no known compatibility issues between these versions.
  • Ensure that your React version meets React-Leaflet v5’s requirements (the documentation indicates React v19).

These dependencies in package.json (lines 14, 62, and 70) are correctly set up.

app/frontend/styles/markers.css (1)

1-70: Well-structured CSS implementation!

The marker styles are well-organized with:

  • Proper use of CSS custom properties for theming
  • Smooth transitions for interactive states
  • Cross-browser compatibility with webkit prefixes

import { NewOsbl } from '@/pages/Contribution/types'
import 'leaflet/dist/leaflet.css'
import { MapContainer, TileLayer, Marker, Tooltip } from 'react-leaflet'
import { Icon, Map, DivIcon } from 'leaflet'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid shadowing the Map type from Leaflet.

The current import shadows the global Map type, which could lead to confusion.

-import { Icon, Map, DivIcon } from 'leaflet'
+import { Icon, DivIcon } from 'leaflet'
+import type { Map as LeafletMap } from 'leaflet'
📝 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.

Suggested change
import { Icon, Map, DivIcon } from 'leaflet'
import { Icon, DivIcon } from 'leaflet'
import type { Map as LeafletMap } from 'leaflet'
🧰 Tools
🪛 Biome (1.9.4)

[error] 4-4: Do not shadow the global "Map" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

- Refactor LocationTab with improved marker interactions and scrolling
- Add responsive padding and layout adjustments for DataSheetTab and Show page
- Move marker styles from separate CSS file to application.css
- Enhance mobile-friendly UI with conditional rendering and flex wrapping
- Add null coalescing for finance year display
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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

♻️ Duplicate comments (9)
app/frontend/components/pages/osbl/show/LocationTab.tsx (9)

4-4: ⚠️ Potential issue

Avoid shadowing the Map type from Leaflet.

The current import shadows the global Map type, which could lead to confusion.

Apply this diff to fix the type shadowing:

-import { Map, DivIcon } from 'leaflet'
+import { DivIcon } from 'leaflet'
+import type { Map as LeafletMap } from 'leaflet'
🧰 Tools
🪛 Biome (1.9.4)

[error] 4-4: Do not shadow the global "Map" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)


183-191: ⚠️ Potential issue

Add security attributes to external links.

The website link should include security-related attributes to protect against potential vulnerabilities.

Apply this diff to fix the security attributes:

<a
  href={location.website}
  target='_blank'
-  rel='noreferrer'
+  rel='noopener noreferrer'
  className='text-sm text-primary hover:underline mt-2 block'
>
  Site web
</a>

217-220: ⚠️ Potential issue

Add validation for latitude and longitude values.

The latitude and longitude values are directly converted to numbers without validation.

Apply this diff to add coordinate validation:

+const isValidCoordinate = (value: number): boolean => {
+  return !isNaN(value) && isFinite(value)
+}

+const lat = Number(location.address_attributes.latitude)
+const lng = Number(location.address_attributes.longitude)
+
+if (!isValidCoordinate(lat) || !isValidCoordinate(lng)) {
+  console.error(`Invalid coordinates for location: ${location.name}`)
+  return null
+}

<Marker
  key={index}
  position={{
-    lat: Number(location.address_attributes.latitude),
-    lng: Number(location.address_attributes.longitude)
+    lat,
+    lng
  }}

1-4: ⚠️ Potential issue

Fix Map type shadowing.

The current import shadows the global Map type from Leaflet, which could lead to confusion.

Apply this diff to fix the type shadowing:

-import { Map, DivIcon } from 'leaflet'
+import { DivIcon } from 'leaflet'
+import type { Map as LeafletMap } from 'leaflet'
🧰 Tools
🪛 Biome (1.9.4)

[error] 4-4: Do not shadow the global "Map" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)


82-95: ⚠️ Potential issue

Add error handling for invalid coordinates.

The handleLocationSelect function directly converts coordinates to numbers without validation.

Apply this diff to add coordinate validation:

+const isValidCoordinate = (value: number): boolean => {
+  return !isNaN(value) && isFinite(value)
+}

const handleLocationSelect = (index: number): void => {
  if (selectedIndex === index) {
    setSelectedIndex(null)
    map?.flyTo(FRANCE_CENTER, FRANCE_ZOOM, { animate: true, duration: 1.5 })
  } else {
    setSelectedIndex(index)
    const location = sortedLocations[index]
+   const lat = Number(location.address_attributes.latitude)
+   const lng = Number(location.address_attributes.longitude)
+   
+   if (!isValidCoordinate(lat) || !isValidCoordinate(lng)) {
+     console.error(`Invalid coordinates for location: ${location.name}`)
+     return
+   }
    
    map?.flyTo(
-     [Number(location.address_attributes.latitude), Number(location.address_attributes.longitude)],
+     [lat, lng],
      LOCATION_ZOOM,
      { animate: true, duration: 1.5 }
    )
  }
}

183-191: ⚠️ Potential issue

Add security attributes to external links.

The website link should include security-related attributes to protect against potential vulnerabilities.

Apply this diff to add security attributes:

<a
  href={location.website}
  target='_blank'
- rel='noreferrer'
+ rel='noopener noreferrer'
  className='text-sm text-primary hover:underline mt-2 block'
>
  Site web
</a>

1-4: ⚠️ Potential issue

Fix Map type shadowing.

The current import shadows the global Map type from Leaflet.

Apply this diff to fix the type shadowing:

-import { Map, DivIcon } from 'leaflet'
+import { DivIcon } from 'leaflet'
+import type { Map as LeafletMap } from 'leaflet'
🧰 Tools
🪛 Biome (1.9.4)

[error] 4-4: Do not shadow the global "Map" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)


183-191: ⚠️ Potential issue

Add security attributes to external links.

The website link should include security-related attributes to protect against potential vulnerabilities.

Apply this diff to fix the security issue:

<a
  href={location.website}
  target='_blank'
-  rel='noreferrer'
+  rel='noopener noreferrer'
  className='text-sm text-primary hover:underline mt-2 block'
>
  Site web
</a>

217-220: ⚠️ Potential issue

Add validation for latitude and longitude values.

The latitude and longitude values are directly converted to numbers without validation.

Apply this diff to add coordinate validation:

+const isValidCoordinate = (value: number): boolean => {
+  return !isNaN(value) && isFinite(value)
+}

+const lat = Number(location.address_attributes.latitude)
+const lng = Number(location.address_attributes.longitude)
+
+if (!isValidCoordinate(lat) || !isValidCoordinate(lng)) {
+  console.error(`Invalid coordinates for location: ${location.name}`)
+  return null
+}

<Marker
  key={index}
  position={{
-    lat: Number(location.address_attributes.latitude),
-    lng: Number(location.address_attributes.longitude)
+    lat,
+    lng
  }}
🧹 Nitpick comments (6)
app/frontend/components/pages/osbl/show/GestionTab.tsx (2)

35-43: Consider extracting the default finance object to a constant.

The default finance object could be extracted to a named constant at the top of the file for better maintainability and reusability.

+const DEFAULT_FINANCE = {
+  year: undefined,
+  budget: undefined,
+  treasury: undefined,
+  certified: undefined,
+  employees_count: undefined,
+  fund_allocations_attributes: undefined,
+  fund_sources_attributes: undefined
+} as const;

 export default function GestionTab ({ osbl }: Props): React.ReactElement {
-  const sortedFinances = osbl.annual_finances_attributes?.sort((a, b) => a.year - b.year) ?? [{
-    year: undefined,
-    budget: undefined,
-    treasury: undefined,
-    certified: undefined,
-    employees_count: undefined,
-    fund_allocations_attributes: undefined,
-    fund_sources_attributes: undefined
-  }]
+  const sortedFinances = osbl.annual_finances_attributes?.sort((a, b) => a.year - b.year) ?? [DEFAULT_FINANCE]

193-210: Consider extracting carousel navigation buttons to a component.

The carousel navigation buttons are duplicated with different styles for mobile/desktop views. Consider extracting them into a reusable component to reduce code duplication.

+interface CarouselNavigationProps {
+  isMobile?: boolean;
+}
+
+function CarouselNavigation({ isMobile = false }: CarouselNavigationProps) {
+  const baseProps = {
+    variant: 'secondary' as const,
+  };
+  
+  const mobileProps = {
+    ...baseProps,
+    size: 'lg' as const,
+  };
+  
+  if (isMobile) {
+    return (
+      <>
+        <CarouselPrevious
+          {...mobileProps}
+          className='top-4 -mr-2 left-auto right-1/2 -translate-x-[86px] lg:hidden'
+        />
+        <CarouselNext
+          {...mobileProps}
+          className='top-4 -ml-2 left-1/2 translate-x-[86px] lg:hidden'
+        />
+      </>
+    );
+  }
+  
+  return (
+    <>
+      <CarouselPrevious
+        {...baseProps}
+        className='-ml-6 hidden lg:flex'
+      />
+      <CarouselNext
+        {...baseProps}
+        className='-mr-6 hidden lg:flex'
+      />
+    </>
+  );
+}

 // Replace the existing navigation buttons with:
 <CarouselNavigation isMobile />
 <CarouselNavigation />
app/frontend/components/pages/osbl/show/LocationTab.tsx (4)

66-72: Consider adding a more informative empty state.

The current empty state message could be more helpful by suggesting actions or providing context.

Apply this diff to enhance the empty state:

-      <div className='text-center text-muted-foreground py-4'>
-        Aucune localisation n'a été renseignée.
-      </div>
+      <div className='text-center text-muted-foreground py-8 space-y-2'>
+        <MapIcon className='mx-auto h-12 w-12 text-muted-foreground/50' />
+        <div>Aucune localisation n'a été renseignée.</div>
+        <div className='text-sm'>
+          Ajoutez des localisations pour visualiser les emplacements sur la carte.
+        </div>
+      </div>

97-121: Consider debouncing the hover effect.

The marker hover effect triggers immediate scrolling, which could be jarring with rapid mouse movements.

Consider using a debounce hook to smooth out the scroll behavior:

const debouncedHandleMarkerHover = useDebounce((index: number | null, fromMarker = false) => {
  // existing hover logic
}, 150)

15-17: Consider extracting map constants to a configuration file.

These map-related constants could be moved to a separate configuration file for better maintainability and reusability.


15-17: Consider making map constants configurable.

The map center coordinates and zoom levels are hardcoded. Consider making these configurable through environment variables or a configuration file for better maintainability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b5bf3e5 and b1d28e5.

📒 Files selected for processing (5)
  • app/frontend/components/pages/osbl/show/DataSheetTab.tsx (1 hunks)
  • app/frontend/components/pages/osbl/show/GestionTab.tsx (1 hunks)
  • app/frontend/components/pages/osbl/show/LocationTab.tsx (1 hunks)
  • app/frontend/entrypoints/application.css (1 hunks)
  • app/frontend/pages/Osbl/Show.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/frontend/pages/Osbl/Show.tsx
👮 Files not reviewed due to content moderation or server errors (3)
  • app/frontend/components/pages/osbl/show/DataSheetTab.tsx
  • app/frontend/components/pages/osbl/show/LocationTab.tsx
  • app/frontend/entrypoints/application.css
🧰 Additional context used
🪛 Biome (1.9.4)
app/frontend/components/pages/osbl/show/LocationTab.tsx

[error] 4-4: Do not shadow the global "Map" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: deploy
🔇 Additional comments (8)
app/frontend/components/pages/osbl/show/GestionTab.tsx (1)

60-60: LGTM! Good defensive programming practice.

The addition of the nullish coalescing operator (??) improves the robustness of the UI by gracefully handling undefined/null values for finance.year.

app/frontend/components/pages/osbl/show/DataSheetTab.tsx (3)

54-55: LGTM! Responsive design improvements.

The changes enhance the mobile experience by reducing padding and grid gaps on smaller screens while maintaining the original spacing on larger screens.


54-55: LGTM! Responsive design improvements.

The changes enhance the component's responsiveness by reducing padding and grid gaps on smaller screens while maintaining the original spacing on larger screens.


54-55: LGTM! Responsive design improvements.

The padding and gap adjustments enhance the mobile experience while maintaining the original layout on larger screens.

app/frontend/entrypoints/application.css (3)

199-267: LGTM! Well-structured marker styling implementation.

The CSS implementation for markers is well-organized and includes:

  • Proper use of CSS variables for colors
  • SVG mask for consistent marker shape
  • Smooth transitions for active state
  • Accessible color contrast with white inner circle

199-267: LGTM! Well-structured marker styles.

The marker styles are well-implemented with:

  • Proper use of CSS variables for colors
  • Smooth transitions for interactive states
  • Cross-browser compatibility with vendor prefixes
  • Efficient use of SVG masks for marker shapes

199-267: LGTM! Well-structured marker styles.

The marker styles are well-implemented with:

  • Clean base styles and reusable components
  • Efficient use of CSS variables for colors
  • Smooth transitions and animations
  • Cross-browser compatibility with vendor prefixes
app/frontend/components/pages/osbl/show/LocationTab.tsx (1)

210-213:

❓ Verification inconclusive

Verify OpenStreetMap tile server usage terms.

Ensure compliance with OpenStreetMap's tile usage policy and consider implementing tile caching for better performance.


🌐 Web query:

What are the usage terms and rate limits for OpenStreetMap tile servers?

💡 Result:

OpenStreetMap's tile servers have flexible usage terms designed to protect shared resources while allowing creative applications. Here's a detailed breakdown:

Usage Terms

Core principles ([8][7]):

  • Free data ≠ free infrastructure: While OSM data is open (ODbL license), tile servers require significant resources to maintain
  • No SLA guarantees: Service availability is "best effort" with possible interruptions
  • Commercial caution: Explicitly advised against using OSMF tiles for business-critical applications

Key restrictions ([8][6][4]):

  1. Bulk downloading prohibited: Prefetching tiles for offline use/archives is forbidden
  2. Attribution required: Must display "© OpenStreetMap contributors"
  3. Technical requirements:
    • Use HTTPS with tile.openstreetmap.org URL structure
    • Set descriptive User-Agent headers
    • Follow standard web browser request patterns

Rate Limiting Approach

No predefined thresholds ([1][8]):

  • Blocks occur reactively based on server load rather than fixed request counts
  • Factors influencing blocking include:
    • Request patterns (geographic concentration, zoom levels)
    • Concurrent connections
    • Overall server capacity at time of requests

Mitigation strategies:

  • For >15k monthly views: Use commercial providers like Geofabrik (~€420/yr) or MapTiler ($25/mo)[4]
  • High-volume users: Set up private tile servers using open-source tools like OpenMapTiles[3][5]
  • Emergency fallback: Implement map provider switching logic in applications[4]

Commercial Considerations

While small-scale usage might work temporarily ([1][4]), official guidance strongly recommends:

// Example provider fallback implementation
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap',
    maxZoom: 19,
    subdomains: 'abc',
    errorTileUrl: '/maptiler-fallback/{z}/{x}/{y}.png' // Backup tilesource
});

Cost comparison ([4][5]):

Service Type Cost Estimate Reliability
OSMF Public Free No SLA
Self-Hosted $200-$500/mo Full control
Commercial $25-$500/mo SLA options

The system prioritizes editing operations over tile serving – heavy mapping activity could temporarily reduce tile availability[8]. For sustainable scaling, combining multiple tile sources while respecting individual providers' policies remains the recommended approach[3][5].

Citations:


Action Required: Verify Compliance with OpenStreetMap Usage Guidelines

Your current implementation uses the standard OSM tile URL and proper attribution, which aligns with OSM’s basic requirements. However, please make sure that:

  • Your usage strictly adheres to OSM's terms (especially avoiding bulk downloading).
  • You configure your application (e.g., User-Agent handling) to follow OSM’s technical guidelines.
  • For production or high-traffic scenarios, consider implementing a caching strategy or evaluating alternative tile providers to handle potential rate limiting or load issues.

- Split LocationTab into LocationsList, OsblMap, and useLocationInteractions
- Extract location sorting and marker rendering logic into separate functions
- Add utility functions for location type and marker styling
- Improve code organization and readability
- Enhance location display and map interaction components
Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 (8)
app/frontend/lib/utils.ts (1)

8-10: Add input validation and documentation.

The implementation is clean and efficient. Consider these improvements:

  1. Add JSDoc documentation to describe the function's purpose and parameters
  2. Add input validation for edge cases
+/**
+ * Capitalizes the first character of a string.
+ * @param str - The string to capitalize
+ * @returns The capitalized string
+ * @example
+ * capitalize('hello') // returns 'Hello'
+ * capitalize('') // returns ''
+ */
 export function capitalize (str: string): string {
+  if (!str) return str
   return `${str.charAt(0).toUpperCase()}${str.slice(1)}`
 }
app/frontend/components/pages/osbl/show/LocationTab/LocationsList/LocationItemContent.tsx (1)

6-17: Add type safety for location types.

The getLocationBadgeVariant function uses string literals without type safety. Consider using an enum or union type for location types.

+type LocationType = 'Siège social' | 'Antenne locale' | "Lieu d'activité"
+
-function getLocationBadgeVariant (type: string): 'default' | 'secondary' | 'destructive' | 'outline' {
+function getLocationBadgeVariant (type: LocationType): 'default' | 'secondary' | 'destructive' | 'outline' {
app/frontend/components/pages/osbl/show/LocationTab.tsx (1)

13-15: Move constants to a configuration file.

Consider moving map-related constants to a separate configuration file for better maintainability.

Create a new file config/map.ts:

export const MAP_CONFIG = {
  FRANCE_CENTER: [46.603354, 1.888334] as [number, number],
  FRANCE_ZOOM: 5,
  LOCATION_ZOOM: 13
} as const
app/frontend/components/pages/osbl/show/LocationTab/OsblMap.tsx (4)

1-2: Avoid shadowing the global Map type.

Rename the imported Map type to be more specific and avoid confusion with the global Map object.

-import { Map, DivIcon } from 'leaflet'
+import { Map as LeafletMap, DivIcon } from 'leaflet'
🧰 Tools
🪛 Biome (1.9.4)

[error] 2-2: Do not shadow the global "Map" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)


18-29: Enhance type safety for location types.

Consider using an enum or union type for location types to prevent typos and improve maintainability.

+type LocationType = 'Siège social' | 'Antenne locale' | "Lieu d'activité";

-function getLocationMarkerClass (type: string): string {
+function getLocationMarkerClass (type: LocationType): string {
   switch (type) {
     case 'Siège social':
       return 'marker-siege'
     case 'Antenne locale':
       return 'marker-antenne'
     case "Lieu d'activité":
       return 'marker-default'
-    default:
-      return 'marker-lieu'
   }
 }

62-65: Move tile layer URL to configuration.

Consider moving the OpenStreetMap URL to a configuration file for better maintainability and flexibility.

+// In config.ts
+export const MAP_TILE_URL = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
+export const MAP_ATTRIBUTION = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors';

// In OsblMap.tsx
<TileLayer
-  attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
-  url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
+  attribution={MAP_ATTRIBUTION}
+  url={MAP_TILE_URL}
/>

66-87: Optimize marker rendering performance.

Consider memoizing the markers to prevent unnecessary re-renders.

+import { useMemo } from 'react';

export function OsblMap ({ ... }: OsblMapProps): React.ReactElement {
+  const markers = useMemo(() => 
+    locations.map((location, index) => (
       <Marker
         key={index}
         position={{
           lat: Number(location.address_attributes.latitude),
           lng: Number(location.address_attributes.longitude)
         }}
         icon={getMarkerIcon(
           location.type,
           selectedIndex === index || hoveredIndex === index
         )}
         eventHandlers={{
           click: () => onLocationSelect(index),
           mouseover: () => onMarkerHover(index, true),
           mouseout: () => onMarkerHover(null)
         }}
       >
         <Tooltip direction='top' offset={[0, -20]} permanent={selectedIndex === index}>
           {location.name !== undefined ? capitalize(location.name) : location.type}
         </Tooltip>
       </Marker>
     )),
+    [locations, selectedIndex, hoveredIndex]
+  );

   return (
     ...
-    {locations.map((location, index) => (...))}
+    {markers}
     ...
   );
}
app/frontend/entrypoints/application.css (1)

213-228: Consider adding fallback for mask-image.

Some older browsers might not support mask-image. Consider adding a fallback shape.

.marker-pin::before {
+  /* Fallback for browsers that don't support mask-image */
+  border-radius: 50% 50% 50% 0;
+  transform: rotate(-45deg);
   mask-image: url("data:image/svg+xml,...");
   -webkit-mask-image: url("data:image/svg+xml,...");
   ...
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b1d28e5 and c54c0ab.

📒 Files selected for processing (7)
  • app/frontend/components/pages/osbl/show/LocationTab.tsx (1 hunks)
  • app/frontend/components/pages/osbl/show/LocationTab/LocationsList.tsx (1 hunks)
  • app/frontend/components/pages/osbl/show/LocationTab/LocationsList/LocationItemContent.tsx (1 hunks)
  • app/frontend/components/pages/osbl/show/LocationTab/OsblMap.tsx (1 hunks)
  • app/frontend/components/pages/osbl/show/LocationTab/useLocationInteractions.ts (1 hunks)
  • app/frontend/entrypoints/application.css (1 hunks)
  • app/frontend/lib/utils.ts (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
app/frontend/components/pages/osbl/show/LocationTab/OsblMap.tsx

[error] 2-2: Do not shadow the global "Map" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

app/frontend/components/pages/osbl/show/LocationTab/useLocationInteractions.ts

[error] 2-2: Do not shadow the global "Map" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

app/frontend/components/pages/osbl/show/LocationTab.tsx

[error] 3-3: Do not shadow the global "Map" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: deploy
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (6)
app/frontend/components/pages/osbl/show/LocationTab/LocationsList/LocationItemContent.tsx (1)

50-58: Add security attributes to external links.

The website link should include security-related attributes to protect against potential vulnerabilities.

<a
  href={location.website}
  target='_blank'
- rel='noreferrer'
+ rel='noopener noreferrer'
  className='text-sm text-primary hover:underline mt-2 block'
>
app/frontend/components/pages/osbl/show/LocationTab.tsx (1)

1-3: Avoid shadowing the Map type from Leaflet.

The current import shadows the global Map type, which could lead to confusion.

import { NewOsbl, Location } from '@/pages/Contribution/types'
import 'leaflet/dist/leaflet.css'
-import { Map } from 'leaflet'
+import type { Map as LeafletMap } from 'leaflet'
🧰 Tools
🪛 Biome (1.9.4)

[error] 3-3: Do not shadow the global "Map" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

app/frontend/components/pages/osbl/show/LocationTab/useLocationInteractions.ts (2)

1-2: Avoid shadowing the Map type from Leaflet.

The current import shadows the global Map type, which could lead to confusion.

import { useCallback } from 'react'
-import { Map } from 'leaflet'
+import type { Map as LeafletMap } from 'leaflet'
🧰 Tools
🪛 Biome (1.9.4)

[error] 2-2: Do not shadow the global "Map" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)


40-44: Add validation for latitude and longitude values.

The latitude and longitude values are directly converted to numbers without validation.

+const isValidCoordinate = (value: number): boolean => {
+  return !isNaN(value) && isFinite(value)
+}

+const lat = Number(location.address_attributes.latitude)
+const lng = Number(location.address_attributes.longitude)
+
+if (!isValidCoordinate(lat) || !isValidCoordinate(lng)) {
+  console.error(`Invalid coordinates for location: ${location.name}`)
+  return
+}

map?.flyTo(
-  [Number(location.address_attributes.latitude), Number(location.address_attributes.longitude)],
+  [lat, lng],
  LOCATION_ZOOM,
  { animate: true, duration: 1.5 }
)
app/frontend/entrypoints/application.css (2)

199-211: LGTM! Well-structured marker base styles.

The marker base styles are well-organized with proper positioning and smooth transitions.


242-257: LGTM! Clear color variant implementation.

The color variants are well-organized and use semantic CSS variables.

- Remove unnecessary Number() conversion for latitude and longitude
- Update rel attribute to use 'noopener noreferrer' for external links
- Simplify city attribute handling in osblData transformation
@mpressen mpressen merged commit 3894944 into main Feb 21, 2025
7 of 10 checks passed
@mpressen mpressen deleted the 189-tab-implantation branch February 21, 2025 11:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

tab Implantation
1 participant