Skip to content

RR-T45 Adding in POI Support#115

Merged
ucswift merged 4 commits into
masterfrom
develop
Apr 27, 2026
Merged

RR-T45 Adding in POI Support#115
ucswift merged 4 commits into
masterfrom
develop

Conversation

@ucswift
Copy link
Copy Markdown
Member

@ucswift ucswift commented Apr 26, 2026

Summary by CodeRabbit

  • New Features
    • Added Points of Interest (POI) browsing and destination selection for calls.
    • Introduced tabbed map interface with separate Map and POI viewing tabs.
    • Enhanced call creation, editing, and personnel status workflows with POI destination support.
    • Added POI detail screens with map navigation and directions capabilities.
    • Improved destination information display across call details and unit status.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 26, 2026

Warning

Rate limit exceeded

@ucswift has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 16 minutes and 26 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 406733f2-be59-46a8-8aa0-c4d54fc8a146

📥 Commits

Reviewing files that changed from the base of the PR and between 67bc77e and 6aa3ad7.

📒 Files selected for processing (14)
  • __mocks__/lucide-react-native.ts
  • src/app/call/new/index.tsx
  • src/lib/status-destinations.ts
  • src/models/offline-queue/queued-event.ts
  • src/stores/status/personnel-status-store.ts
  • src/translations/ar.json
  • src/translations/de.json
  • src/translations/en.json
  • src/translations/es.json
  • src/translations/fr.json
  • src/translations/it.json
  • src/translations/pl.json
  • src/translations/sv.json
  • src/translations/uk.json
📝 Walkthrough

Walkthrough

This pull request introduces Points of Interest (POI) functionality across the dispatch system, including new API endpoints for fetching POI data, POI selection workflows in call creation/editing and personnel status, a new POI detail screen, map-based POI display with filtering/sorting, and comprehensive translation updates across 10 languages.

Changes

Cohort / File(s) Summary
POI API & Models
src/api/mapping/mapping.ts, src/models/v4/mapping/*, src/api/dispatch/index.ts
Adds POI API endpoints (getPoiTypes, getPois, getPoi) and new data models (PoiResultData, PoiTypeResultData, PoiLayerData) to represent POI entity structures and API responses.
POI Store & Utilities
src/stores/poi/store.ts, src/lib/poi.ts, src/lib/status-destinations.ts
Introduces Zustand-based POI store for managing POI data, caching, and detail fetching; adds POI display/selection helpers and destination type/tab logic.
Call API & Models Enhancement
src/api/calls/calls.ts, src/models/v4/calls/callResultData.ts, src/models/v4/dispatch/newCallFormResultData.ts, src/models/v4/dispatch/getSetUnitStateResultData.ts
Extends call creation/update payloads with destinationPoiId field; enriches call and dispatch result models with destination metadata (POI id, name, address, coordinates).
Map UI Refactor
src/app/(app)/map.tsx, src/components/maps/map-panel.tsx, src/components/maps/poi-list-panel.tsx, src/components/maps/map-pins.tsx, src/components/maps/pin-marker.tsx, src/constants/map-icons.ts, src/app/(app)/__tests__/map.test.tsx
Replaces monolithic map screen with tabbed interface (Map/POIs); extracts map rendering into MapPanel with camera/marker/SignalR integration; adds PoiListPanel with filtering/sorting; refactors icon resolution logic.
POI Detail Screen
src/app/poi/[id].tsx
New route-driven screen displaying POI details (location, type, metadata) with map view, directions action, and conditional destination-setting action.
Call Creation & Editing
src/app/call/new/index.tsx, src/app/call/[id]/edit.tsx, src/app/call/[id]/index.tsx
Adds optional POI destination selection to new/edit call forms with async POI/type loading; call detail screen now displays destination with route action.
Personnel Status Flow
src/components/status/personnel-status-bottom-sheet.tsx, src/stores/status/personnel-status-store.ts, src/components/status/__tests__/personnel-status-bottom-sheet.test.tsx, src/stores/status/__tests__/personnel-status-store.test.ts
Extends status selection workflow with POI destination tab; adds select-status preliminary step; refactors destination selection logic using new type/utility system; updates analytics payloads.
Unit Status Display
src/components/home/user-status-card.tsx, src/components/units/unit-card.tsx, src/components/units/unit-details-sheet.tsx, src/models/v4/unitStatus/unitStatusResultData.ts
Displays current destination name in user/unit status cards; enriches UnitStatusResultData with destination metadata fields.
Status Helpers & Models
src/models/v4/statuses/customStateDetailTypes.ts, src/models/v4/statuses/destinationEntityTypes.ts, src/models/v4/personnelStatuses/getCurrentStatusResultData.ts, src/models/v4/personnelStatuses/savePersonStatusInput.ts, src/models/v4/personnelStatuses/savePersonsStatusesInput.ts, src/models/v4/unitStatus/saveUnitStatusInput.ts
Introduces destination type enums and detail type mappings; extends save/get status models with responding-to-type fields.
Offline Queue & Services
src/models/offline-queue/queued-event.ts, src/services/offline-event-manager.service.ts
Adds respondingToType and eventId fields to queued unit status events for offline persistence and replay.
Navigation & UI Polish
src/app/_layout.tsx, src/components/home/status-buttons.tsx, src/components/home/__tests__/status-buttons.test.tsx, src/components/status/__tests__/personnel-status-bottom-sheet.test.tsx, src/components/ui/shared-tabs.tsx
Registers POI route in root stack; refactors status-button filtering logic into constant; updates tab styling and test mocks.
Translations (All Languages)
src/translations/{en,ar,de,es,fr,it,pl,sv,uk}.json
Adds POI-related strings across destination labels, POI filtering/sorting, error/loading states, and personnel status messaging in 9+ languages.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MapUI as Map Screen
    participant PoiStore
    participant API
    participant MapPanel
    participant SignalR

    User->>MapUI: Navigate to map with tab=pois
    MapUI->>PoiStore: fetchPoisData()
    PoiStore->>API: getPoiTypes() & getPois()
    API-->>PoiStore: POI types & list
    PoiStore->>PoiStore: Build poiDetailsById cache
    PoiStore-->>MapUI: poiTypes, pois loaded
    
    MapUI->>MapPanel: Render map with POI pins
    SignalR->>MapPanel: Real-time marker updates
    MapPanel->>MapPanel: Refresh pins from store
    
    User->>MapPanel: Press POI pin
    MapPanel->>MapUI: Navigate to /poi/[id]
    MapUI->>PoiStore: fetchPoiDetail(poiId)
    PoiStore->>API: getPoi(poiId)
    API-->>PoiStore: POI details
    PoiStore-->>MapUI: Display POI detail screen
Loading
sequenceDiagram
    participant User
    participant StatusUI as Status Bottom Sheet
    participant StatusStore
    participant API
    participant DestinationTab

    User->>StatusUI: Open status update
    StatusUI->>StatusStore: setIsOpen(true, status)
    StatusStore->>StatusStore: Check requiresStatusSelection
    alt Status detail allows POIs
        StatusUI->>DestinationTab: Show POI selection tab
        User->>DestinationTab: Select POI
        DestinationTab->>StatusStore: setSelectedPoi(poi)
        StatusStore->>StatusStore: Clear call/station, set selectedPoi
    end
    
    User->>StatusUI: Submit status
    StatusUI->>StatusStore: submitStatus()
    StatusStore->>StatusStore: Build RespondingTo, RespondingToType, EventId
    StatusStore->>API: savePersonStatus(input)
    API-->>StatusStore: Success
    StatusStore-->>User: Toast confirmation
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 Hops through the POI fields so green,
Where places of interest gleam and convene,
Dispatch flows now pivot on pinned location,
Maps bloom with tabs—a cartographic sensation!
Responders select destinations with care,
Status and routes now dance through the air. 🗺️✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective of this comprehensive changeset, which adds POI (Point of Interest) support throughout the application.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@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: 13

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/app/(app)/__tests__/map.test.tsx (1)

330-334: ⚠️ Potential issue | 🟡 Minor

Remove duplicate mockTrackEvent.mockReset() calls.

mockTrackEvent.mockReset() is called five times in a row; only the first is needed (and jest.clearAllMocks() on line 328 already clears it).

Proposed fix
     jest.clearAllMocks();
     mockRouterPush.mockReset();
     mockTrackEvent.mockReset();
-    mockTrackEvent.mockReset();
-    mockTrackEvent.mockReset();
-    mockTrackEvent.mockReset();
-    mockTrackEvent.mockReset();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(app)/__tests__/map.test.tsx around lines 330 - 334, Remove the
redundant consecutive calls to mockTrackEvent.mockReset(): keep at most one call
(or remove all since jest.clearAllMocks() already runs) to avoid duplicate
resets; update the test to either delete the four extra
mockTrackEvent.mockReset() invocations or remove the single remaining call if
you rely on jest.clearAllMocks(), ensuring you only call
mockTrackEvent.mockReset() (or none) around the mock setup/teardown in the test
that uses mockTrackEvent and jest.clearAllMocks().
src/models/offline-queue/queued-event.ts (1)

29-47: ⚠️ Potential issue | 🟡 Minor

Add respondingToType to QueuedPersonnelStatusEvent.data for consistency and type safety.

QueuedUnitStatusEvent.data includes respondingToType?: number | null (line 56), and personnel status submissions populate this field from the destination payload (personnel-status-store.ts:389). However, QueuedPersonnelStatusEvent.data (lines 31–46) lacks this field. This creates a type mismatch: the actual offline data stored includes the responder type, but the interface doesn't declare it, leaving it inaccessible or prone to type errors during queue processing.

Proposed fix
 export interface QueuedPersonnelStatusEvent extends Omit<QueuedEvent, 'data'> {
   type: QueuedEventType.PERSONNEL_STATUS;
   data: {
     userId: string;
     statusType: string;
     note?: string;
     respondingTo?: string;
+    respondingToType?: number | null;
     timestamp: string;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/models/offline-queue/queued-event.ts` around lines 29 - 47,
QueuedPersonnelStatusEvent.data is missing the respondingToType field which
causes a type mismatch with actual stored offline payloads and is inconsistent
with QueuedUnitStatusEvent.data; add respondingToType?: number | null to the
data shape in the QueuedPersonnelStatusEvent interface so it matches
QueuedUnitStatusEvent and the code path that populates respondingToType for
personnel status submissions (look for where respondingToType is assigned during
personnel status enqueueing).
src/components/maps/pin-marker.tsx (1)

21-25: ⚠️ Potential issue | 🟡 Minor

The non-null assertion is unnecessary, but the code is type-safe.

resolveMapIconKey has a return type of MapIconKey, which is defined as keyof typeof MAP_ICONS. This guarantees that every possible return value is a key in MAP_ICONS, so icon can never be undefined. The ! can be safely removed without any runtime risk.

♻️ Suggested change
-      <Image fadeDuration={0} source={icon!.uri} style={[styles.image, { width: size, height: size }]} />
+      <Image fadeDuration={0} source={icon.uri} style={[styles.image, { width: size, height: size }]} />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/maps/pin-marker.tsx` around lines 21 - 25, The code uses a
non-null assertion on icon when rendering the Image, but resolveMapIconKey
returns a MapIconKey (keyof typeof MAP_ICONS) so MAP_ICONS[...] cannot be
undefined; remove the unnecessary `!` on `icon` where it's used in the Image
`source` prop (symbols: MAP_ICONS, resolveMapIconKey, icon, Image) to clean up
the code and rely on the existing type safety.
src/components/status/store.ts (1)

117-149: ⚠️ Potential issue | 🟠 Major

Bootstrap endpoint failure leaves the user with no destination data.

If getSetUnitStatusData(unitId) throws (e.g., transient network error or the endpoint isn't deployed yet for some tenants), control jumps to the catch block on line 143 and the fallback Promise.all([getCalls(), getAllGroups()]) is never attempted. Previously, the legacy flow would have run successfully. Consider catching the bootstrap error and only then falling back to the legacy getCalls/getAllGroups path before surfacing an error.

🛡️ Suggested resilience improvement
   fetchDestinationData: async (unitId: string) => {
     set({ isLoading: true, error: null });
     try {
-      const bootstrapResponse = await getSetUnitStatusData(unitId);
-      const unitStatusData = bootstrapResponse.Data;
-
-      if (unitStatusData) {
-        set({
-          availableCalls: unitStatusData.Calls || [],
-          availableStations: unitStatusData.Stations || [],
-          availablePois: unitStatusData.DestinationPois || [],
-          poiTypes: unitStatusData.PoiTypes || [],
-          isLoading: false,
-        });
-        return;
-      }
+      try {
+        const bootstrapResponse = await getSetUnitStatusData(unitId);
+        const unitStatusData = bootstrapResponse?.Data;
+        if (unitStatusData) {
+          set({
+            availableCalls: unitStatusData.Calls || [],
+            availableStations: unitStatusData.Stations || [],
+            availablePois: unitStatusData.DestinationPois || [],
+            poiTypes: unitStatusData.PoiTypes || [],
+            isLoading: false,
+          });
+          return;
+        }
+      } catch {
+        // fall through to legacy fetch path
+      }

       const [callsResponse, groupsResponse] = await Promise.all([getCalls(), getAllGroups()]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/status/store.ts` around lines 117 - 149, In
fetchDestinationData, don’t let a thrown error from getSetUnitStatusData(unitId)
short-circuit the legacy fallback; instead wrap the bootstrap call in its own
try/catch so that if getSetUnitStatusData fails you proceed to call
Promise.all([getCalls(), getAllGroups()]) and set the legacy data, and only if
both the bootstrap and the fallback fail set the error state; reference
fetchDestinationData, getSetUnitStatusData, getCalls and getAllGroups when
locating where to add the inner try/catch and conditional set logic.
🧹 Nitpick comments (21)
src/translations/ar.json (1)

847-847: Optional: consider Arabic plural forms for results_count for consistency with other keys.

Other keys in this file (e.g., calendar.attendeesCount / attendeesCount_plural, calendar.eventsCount / eventsCount_plural) use the i18next _plural suffix to handle Arabic's plural categories. poi.results_count currently uses only "{{count}} نقطة اهتمام", which may read awkwardly for counts of 0, 2–10, and 11+. If you plan to implement comprehensive plural handling across the file, adding the plural variant would improve consistency.

Non-blocking — en.json also uses a single form today, so this is only worth doing if you plan to localize plurals more thoroughly throughout the translation files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/translations/ar.json` at line 847, The poi.results_count entry only
provides a single Arabic form ("{{count}} نقطة اهتمام"); add Arabic plural
handling consistent with other keys by adding a "poi.results_count_plural"
variant (using the i18next _plural naming convention) that provides the correct
pluralized string(s) for Arabic, and ensure the code that reads this key uses
the plural-aware lookup (same pattern as calendar.attendeesCount /
attendeesCount_plural and calendar.eventsCount / eventsCount_plural) so counts
like 0, 2–10, and 11+ render with appropriate Arabic forms.
src/models/v4/mapping/poiLayerData.ts (1)

1-8: Consider deduplicating with PoiTypeResultData.

PoiLayerData and PoiTypeResultData (src/models/v4/mapping/poiTypeResultData.ts) have identical field shapes. If they're truly the same payload, alias one to the other (export type PoiLayerData = PoiTypeResultData) to avoid drift; if they're semantically distinct and only happen to align today, keep both but a brief comment documenting the distinction would help future maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/models/v4/mapping/poiLayerData.ts` around lines 1 - 8, PoiLayerData
duplicates PoiTypeResultData; open src/models/v4/mapping/poiTypeResultData.ts to
verify shape and if identical replace the class declaration for PoiLayerData
with a type alias (export type PoiLayerData = PoiTypeResultData) and remove the
duplicate class, ensuring any imports/exports still work; if they are
semantically different keep the PoiLayerData class but add a brief comment above
the class explaining why it differs from PoiTypeResultData (what semantic
distinction exists) to prevent future drift.
src/models/v4/unitStatus/unitStatusResultData.ts (1)

16-19: Mixed-type defaults are inconsistent with the union types.

  • Latitude/Longitude are now optional+nullable number | null — good, matches existing consumer null checks (e.g. Number.isFinite(pin.Latitude) in pin-detail-modal.tsx).
  • GroupId: number | string = '' defaults to an empty string for a numeric-or-string union. Consumers branching on typeof GroupId === 'number' will silently miss the empty-string sentinel. Consider GroupId?: number | string (undefined-as-missing) for clearer semantics.
♻️ Suggested adjustment
-  public GroupId: number | string = '';
+  public GroupId?: number | string;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/models/v4/unitStatus/unitStatusResultData.ts` around lines 16 - 19, The
GroupId field currently uses a mixed-type default string (GroupId: number |
string = ''), which breaks consumers that detect missing numeric IDs via typeof
checks; change GroupId to be optional (GroupId?: number | string) and remove the
empty-string default so absence is represented by undefined, and leave Latitude
and Longitude as optional nullable numbers (Latitude?: number | null,
Longitude?: number | null) to preserve existing Number.isFinite checks; update
any constructors or initializers in the same class (unitStatusResultData / the
class containing GroupId, GroupName, Latitude, Longitude) to stop assigning ''
to GroupId.
src/models/v4/personnelStatuses/getCurrentStatusResultData.ts (1)

8-12: Inconsistency with UnitStatusResultData shape for DestinationType.

DestinationType is declared as required (with null default) here, but in src/models/v4/unitStatus/unitStatusResultData.ts it is declared optional (DestinationType?: number | string | null). The two parallel models should match to avoid surprises in serialization/destructuring code that handles both shapes generically.

♻️ Suggested alignment
   public DestinationId: number | string | null = null;
-  public DestinationType: number | string | null = null;
+  public DestinationType?: number | string | null;
   public DestinationName?: string;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/models/v4/personnelStatuses/getCurrentStatusResultData.ts` around lines 8
- 12, DestinationType in getCurrentStatusResultData is declared as required but
should match the UnitStatusResultData shape; change the field declaration for
DestinationType to be optional (DestinationType?: number | string | null) in the
class/interface so it aligns with UnitStatusResultData, and adjust any
constructor/initialization that enforces a non-optional default (e.g., remove
forced null assignment) to avoid serialization/destructuring mismatches when
handling both types generically.
src/components/units/unit-card.tsx (1)

91-91: Optional: translation key namespace mismatch.

The unit card pulls its label from t('call_detail.destination'), but the unit card is not in the call-detail namespace. Reusing it works today (both copies happen to match), but it couples unit UI copy to call-detail translations and risks divergence. Consider either a shared common.destination key or a units.destination key for cleaner ownership.

Also applies to: 125-129

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/units/unit-card.tsx` at line 91, The translation key used in
the UnitCard component should not reference the call-detail namespace; update
the lookup in src/components/units/unit-card.tsx (e.g., where destinationText is
computed and the other t('call_detail.destination') usages around lines 125-129)
to use a neutral key such as t('common.destination') or a unit-specific key
t('units.destination') instead, and add the corresponding key/value to the
appropriate i18n resource file so both places use the same shared key and
ownership is clear.
src/components/ui/shared-tabs.tsx (1)

117-125: Dead text-* classes in variantStyles.

After the refactor, text color is exclusively driven by getTitleClassName(index) on the inner <Text>. The text-primary-500, text-white, and text-gray-400/500 utility classes still embedded in variantStyles are applied to the Pressable and don't propagate to the <Text>, so they're effectively dead. Consider removing them so the source of truth for text styling is unambiguous.

♻️ Suggested cleanup
     const variantStyles = {
-      default: isActive ? 'border-b-2 border-primary-500 text-primary-500' : `border-b-2 border-transparent ${colorScheme === 'dark' ? 'text-gray-400' : 'text-gray-500'}`,
-      pills: isActive ? 'bg-primary-500 text-white rounded-full' : `bg-transparent ${colorScheme === 'dark' ? 'text-gray-400' : 'text-gray-500'}`,
-      underlined: isActive ? 'border-b-2 border-primary-500 text-primary-500' : `border-b-2 border-transparent ${colorScheme === 'dark' ? 'text-gray-400' : 'text-gray-500'}`,
+      default: isActive ? 'border-b-2 border-primary-500' : 'border-b-2 border-transparent',
+      pills: isActive ? 'bg-primary-500 rounded-full' : 'bg-transparent',
+      underlined: isActive ? 'border-b-2 border-primary-500' : 'border-b-2 border-transparent',
       segmented: isActive ? 'bg-primary-600 shadow-sm dark:bg-primary-500' : 'bg-transparent',
     }[variant];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/shared-tabs.tsx` around lines 117 - 125, The variantStyles
object inside the getTabClassName (in shared-tabs.tsx) still includes text-*
utility classes (e.g., text-primary-500, text-white, text-gray-400/500) that no
longer affect the inner <Text> (which uses getTitleClassName(index))—remove
those text-* classes from all variant keys (default, pills, underlined,
segmented) so variantStyles only controls layout/box styles (borders,
background, rounding, shadow) and keep text color as the single source of truth
in getTitleClassName; ensure tabClassName/baseStyles/sizeStyles remain
unchanged.
src/components/maps/map-pins.tsx (1)

34-34: Avoid magic number 4 for the POI marker type.

map-panel.tsx already defines POI_MARKER_TYPE = 4. Hard-coding the literal here means the two locations can drift. Extract a shared constant (e.g., in a maps/constants.ts or alongside MapMakerInfoData) and import it in both files.

Proposed fix
-          <PinMarker imagePath={pin.ImagePath} marker={pin.Marker} fallbackIconKey={pin.Type === 4 ? 'flag' : 'call'} title={pin.Title} size={32} onPress={() => onPinPress?.(pin)} />
+          <PinMarker imagePath={pin.ImagePath} marker={pin.Marker} fallbackIconKey={pin.Type === POI_MARKER_TYPE ? 'flag' : 'call'} title={pin.Title} size={32} onPress={() => onPinPress?.(pin)} />

Additionally, the inline arrow () => onPinPress?.(pin) creates a new closure on each render for every pin. Per coding guidelines: "Avoid anonymous functions in renderItem or event handlers". Consider extracting PinMarker into a memoized child that receives pin and a stable onPress callback.

As per coding guidelines: "Avoid anonymous functions in renderItem or event handlers; define callbacks with useCallback or outside render".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/maps/map-pins.tsx` at line 34, Replace the magic literal 4
used in map-pins.tsx for POI detection: create a shared constant POI_MARKER_TYPE
(e.g., export from maps/constants.ts or alongside MapMakerInfoData) and import
it into map-pins.tsx, then use POI_MARKER_TYPE instead of pin.Type === 4 when
computing fallbackIconKey; also stop creating an inline arrow for onPress by
passing a stable callback—either wrap the pin item in a memoized child component
that accepts pin and a stable onPress, or define a useCallback handler (e.g.,
handlePinPress) that calls onPinPress?.(pin) so PinMarker receives a stable
reference for its onPress prop.
src/components/home/user-status-card.tsx (1)

63-67: Use a translation key scoped to this component's feature.

t('call_detail.destination') borrows a key from the call-detail feature for a home/user-status component. If call_detail.destination is later renamed or its wording diverges (e.g., capitalization, punctuation suited for the call detail screen), this card will silently change too. Prefer a key under home.user.* (or a shared common.* namespace) for a stable contract.

As per coding guidelines: "All user-visible text must be wrapped in t() from react-i18next. Translation files are in src/translations."

♻️ Suggested change
-          <Text className="text-xs text-gray-500">
-            {t('call_detail.destination')}: {destinationText}
-          </Text>
+          <Text className="text-xs text-gray-500">
+            {t('home.user.destination')}: {destinationText}
+          </Text>

Add a matching home.user.destination entry in src/translations/*.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/home/user-status-card.tsx` around lines 63 - 67, Replace the
cross-feature translation key t('call_detail.destination') used in the
UserStatusCard component with a component-scoped key (e.g.,
t('home.user.destination')) wherever destinationText is rendered, and add
corresponding entries for home.user.destination in all translation files under
src/translations (keeping pluralization/formatting consistent); ensure the new
key is used consistently in the JSX that references destinationText so the card
no longer relies on call_detail.destination.
src/app/call/new/index.tsx (1)

209-215: Use the project logger instead of console.error.

The rest of the codebase routes errors through @/lib/logging logger (e.g. src/app/call/[id]/index.tsx handleDestinationRoute failure path). console.error bypasses centralized logging/telemetry.

♻️ Proposed change
+import { logger } from '@/lib/logging';
@@
-        console.error('Error loading call destination POIs:', error);
+        logger.error({
+          message: 'Error loading call destination POIs',
+          context: { error },
+        });
         toast.error(t('calls.destination_load_error'));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/call/new/index.tsx` around lines 209 - 215, Replace the console.error
call in the catch block that handles loading call destination POIs with the
project's centralized logger: import logger from '@/lib/logging' if not already
present and call logger.error with a descriptive message and the caught error
(preserving the existing abortController.signal.aborted check and
toast.error(t('calls.destination_load_error')) behavior); update the symbol
referenced in the message to indicate this is in the call/new flow so telemetry
can correlate (keep abortController, toast, and t usage unchanged).
src/components/status/__tests__/personnel-status-bottom-sheet.test.tsx (1)

224-225: Use a typed POI array in the mock instead of any[].

This keeps the mock aligned with the real store contract and catches POI shape regressions at compile time.

💡 Suggested change
+import { type PoiResultData } from '@/models/v4/mapping/poiResultData';
@@
-    pois: [] as any[],
+    pois: [] as PoiResultData[],

As per coding guidelines: "Avoid using any; strive for precise types."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/status/__tests__/personnel-status-bottom-sheet.test.tsx`
around lines 224 - 225, Replace the untyped mock POI array (pois: [] as any[])
with a properly typed array using your app's POI type (e.g., change to pois: []
as Poi[] or Array<PointOfInterest>), import the correct POI type from the module
that defines it, and update the test mock where 'pois' (and related
'isLoadingPois') are defined so the mock matches the real store contract and
catches shape regressions at compile time.
src/components/maps/map-panel.tsx (3)

204-219: Pulse animation is never stopped on unmount.

Animated.loop(...).start() keeps running indefinitely. When MapPanel unmounts, the loop continues and may log "useNativeDriver was specified... but native module is missing" warnings during fast refresh, or hold the pulseAnim value alive longer than necessary. Capture the loop and stop it in cleanup.

🛡️ Suggested cleanup
   useEffect(() => {
-    Animated.loop(
+    const loop = Animated.loop(
       Animated.sequence([
         Animated.timing(pulseAnim, { toValue: 1.2, duration: 1000, useNativeDriver: true }),
         Animated.timing(pulseAnim, { toValue: 1, duration: 1000, useNativeDriver: true }),
       ])
-    ).start();
+    );
+    loop.start();
+    return () => loop.stop();
   }, [pulseAnim]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/maps/map-panel.tsx` around lines 204 - 219, The pulse
animation started in MapPanel's useEffect (using Animated.loop ->
Animated.sequence -> Animated.timing with pulseAnim) is never stopped; modify
the effect to capture the returned animation instance (e.g., const pulseLoop =
Animated.loop(...)), call pulseLoop.start(), and return a cleanup function that
calls pulseLoop.stop() to stop the animation on unmount; ensure pulseAnim
remains in the dependency array and that you stop the exact Animated.loop
instance rather than trying to stop individual timings.

42-47: Selector returns a new object on every render — causes unnecessary re-renders of the map.

useLocationStore((state) => ({ latitude, longitude, heading, isMapLocked })) produces a fresh object reference on each store update. Without shallow equality, the entire MapPanel re-renders on any location-store change — and even on totally unrelated state mutations elsewhere in the store. Given how heavy this component is (Mapbox view, animated marker, multiple effects), this is worth fixing.

♻️ Use individual selectors or `useShallow`
-  const location = useLocationStore((state) => ({
-    latitude: state.latitude,
-    longitude: state.longitude,
-    heading: state.heading,
-    isMapLocked: state.isMapLocked,
-  }));
+  const latitude = useLocationStore((state) => state.latitude);
+  const longitude = useLocationStore((state) => state.longitude);
+  const heading = useLocationStore((state) => state.heading);
+  const isMapLocked = useLocationStore((state) => state.isMapLocked);
+  const location = { latitude, longitude, heading, isMapLocked };

Or import useShallow from zustand/react/shallow.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/maps/map-panel.tsx` around lines 42 - 47, The selector passed
to useLocationStore in MapPanel returns a new object each render causing
unnecessary re-renders; change it to select individual primitives or use a
shallow comparator: replace useLocationStore((state) => ({ latitude, longitude,
heading, isMapLocked })) with either multiple useLocationStore calls that each
select latitude, longitude, heading, isMapLocked separately or import and use
useShallow from zustand/react/shallow as the second argument (or wrap the object
selector with shallow) so the MapPanel only re-renders when the actual values
change.

221-228: Avoid any for the camera-changed event.

event: any defeats type safety. @rnmapbox/maps exposes a typed event shape — use it (or at least narrow to { properties?: { isUserInteraction?: boolean } }).

♻️ Suggested types
-  const onCameraChanged = useCallback(
-    (event: any) => {
+  const onCameraChanged = useCallback(
+    (event: { properties?: { isUserInteraction?: boolean } }) => {
       if (event.properties?.isUserInteraction && !isInteractionLocked) {
         setHasUserMovedMap(true);
       }
     },
     [isInteractionLocked]
   );

As per coding guidelines: "Avoid using any; use precise types."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/maps/map-panel.tsx` around lines 221 - 228, The
onCameraChanged callback currently types its parameter as any; replace that with
a precise event type (e.g. import the camera event type from `@rnmapbox/maps` such
as CameraChangedEvent or a matching name) or at minimum narrow it to {
properties?: { isUserInteraction?: boolean } } so TypeScript can check accesses
to event.properties.isUserInteraction; update the function signature for
onCameraChanged and any related imports, keeping the same logic that checks
isUserInteraction and isInteractionLocked and calls setHasUserMovedMap(true).
src/components/maps/poi-list-panel.tsx (2)

83-88: Selector returns a new object every render — will cause excessive re-renders.

usePoiStore((state) => ({ poiTypes, pois, isLoading, error })) returns a fresh object on every store update, defeating zustand's default reference equality and causing this component to re-render whenever any unrelated slice of the store changes (e.g., isLoadingPoi, poiDetailsById).

♻️ Use individual selectors (or pass `shallow` equality)
-  const { poiTypes, pois, isLoading, error } = usePoiStore((state) => ({
-    poiTypes: state.poiTypes,
-    pois: state.pois,
-    isLoading: state.isLoading,
-    error: state.error,
-  }));
+  const poiTypes = usePoiStore((state) => state.poiTypes);
+  const pois = usePoiStore((state) => state.pois);
+  const isLoading = usePoiStore((state) => state.isLoading);
+  const error = usePoiStore((state) => state.error);

Alternatively, import useShallow from zustand/react/shallow and wrap the selector.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/maps/poi-list-panel.tsx` around lines 83 - 88, The selector
passed to usePoiStore in POIListPanel returns a new object each render which
forces extra re-renders; change it to either call usePoiStore separately for
each slice you need (e.g., usePoiStore(state => state.poiTypes),
usePoiStore(state => state.pois), usePoiStore(state => state.isLoading),
usePoiStore(state => state.error)) or keep the combined selector but import and
pass shallow equality (useShallow from zustand/react/shallow) as the second
argument to usePoiStore so the returned object is compared by shallow refs;
update references to poiTypes, pois, isLoading, and error accordingly and remove
the object-returning selector pattern in POIListPanel.

162-162: Avoid inline arrow function in onValueChange.

Define a stable callback (e.g., via useCallback) for the sort Select to match the convention used elsewhere in this file (renderPoiItem, keyExtractor) and avoid recreating the handler each render.

♻️ Proposed refactor
+  const handleSortChange = useCallback((value: string) => {
+    setSortBy(value as PoiSortOption);
+  }, []);
...
-              <Select selectedValue={sortBy} onValueChange={(value) => setSortBy(value as PoiSortOption)}>
+              <Select selectedValue={sortBy} onValueChange={handleSortChange}>

As per coding guidelines: "No anonymous functions in renderItem or event handlers."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/maps/poi-list-panel.tsx` at line 162, The inline arrow used in
the Select's onValueChange should be replaced with a stable callback to avoid
recreating the handler each render; create a memoized function (e.g., const
handleSortChange = useCallback((value: string) => setSortBy(value as
PoiSortOption), [setSortBy]) and pass handleSortChange to the Select's
onValueChange, ensuring you reference the existing state variable sortBy and the
setter setSortBy so the handler is stable like renderPoiItem and keyExtractor.
src/api/mapping/mapping.ts (1)

52-56: Avoid creating a new API endpoint on every getPoi call.

createApiEndpoint is invoked inside getPoi for each POI fetch, which allocates a new client wrapper per call. The other endpoints (getPoiTypesApi, getPoisApi) are hoisted once at module scope. Consider parameterizing the path (e.g., as a path parameter or query) so the endpoint can be created once at module scope, matching the established pattern in this file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/mapping/mapping.ts` around lines 52 - 56, The getPoi function
currently calls
createApiEndpoint(`/Mapping/GetPoi/${encodeURIComponent(poiId)}`) on every
invocation; instead hoist a single API endpoint at module scope (matching
getPoiTypesApi and getPoisApi) by creating createApiEndpoint('/Mapping/GetPoi')
once and change getPoi to call that hoisted endpoint (e.g.,
getPoiApi.get<PoiResult>({ id: poiId } or with a query/param argument) passing
poiId as a parameter or query string) so you stop allocating a new client
wrapper per call; update references in getPoi to use the hoisted endpoint name
(getPoiApi) and pass poiId appropriately.
src/stores/status/personnel-status-store.ts (4)

327-341: Minor: redundant self-assignment in previousStep.

When currentStep === 'select-responding-to' and requiresStatusSelection is false, the ternary on line 332 sets currentStep to 'select-responding-to' (its current value), which is a no-op set that still notifies subscribers. Consider early-returning when there is no previous step:

♻️ Proposed cleanup
       case 'select-responding-to':
-        set({ currentStep: requiresStatusSelection ? 'select-status' : 'select-responding-to' });
-        break;
+        if (requiresStatusSelection) {
+          set({ currentStep: 'select-status' });
+        }
+        break;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/stores/status/personnel-status-store.ts` around lines 327 - 341, The
previousStep handler is performing a redundant set when currentStep ===
'select-responding-to' and requiresStatusSelection === false, causing a no-op
state update and unnecessary subscriber notifications; update previousStep to
early-return in that branch instead of calling set with the same value (i.e.,
check currentStep and requiresStatusSelection at the top of the
'select-responding-to' case and return if no transition is needed) so only
actual state changes go through set; refer to the previousStep function and the
currentStep/requiresStatusSelection variables to locate and modify the logic.

291-308: Silent failure on POI fetch matches fetchGroups, but consider surfacing the error.

The catch resets state to empty arrays without notifying the user, mirroring fetchGroups. From the user's perspective an empty POI list is indistinguishable from "no POIs configured". Consider showing an error toast (using useToastStore) so users can tell when the bottom sheet is empty due to a transient API failure vs. an empty data set, especially since this runs over the network on a request-driven path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/stores/status/personnel-status-store.ts` around lines 291 - 308, The
fetchDestinationPois catch block currently swallows errors and resets poi state;
update it to surface failures by invoking the toast store (useToastStore) inside
the catch: keep setting isLoadingPois to false, avoid silently hiding the error,
and call the toast API (e.g. useToastStore().addToast or the project’s toast
method) with a clear message like "Failed to load POIs" and include the
error.message or stringified error for debugging; mirror the same pattern used
for fetchGroups so users can distinguish an API failure from an empty POI list.

388-388: Redundant || fallback for RespondingTo.

respondingTo state is always kept in sync with the selected entity by setSelectedCall/setSelectedGroup/setSelectedPoi/setResponseType, and destinationPayload.respondingTo is derived from the same selected entity at this point. The || fallback can never substitute a different value than respondingTo once destinations are required (the guard at 354-361 enforces that). Using destinationPayload.respondingTo directly mirrors how respondingToType and eventId are assigned and removes a dead fallback path that could otherwise hide a state-sync bug.

♻️ Proposed simplification
-      status.RespondingTo = respondingTo || destinationPayload.respondingTo;
+      status.RespondingTo = destinationPayload.respondingTo;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/stores/status/personnel-status-store.ts` at line 388, The assignment
status.RespondingTo uses a redundant fallback (status.RespondingTo =
respondingTo || destinationPayload.respondingTo); remove the dead || path and
assign the value directly from destinationPayload.respondingTo to mirror how
respondingToType and eventId are set. Update the assignment in the same block
where status.RespondingTo is set and ensure related state sync points
(setSelectedCall / setSelectedGroup / setSelectedPoi / setResponseType) remain
the source of truth so the code reads: status.RespondingTo =
destinationPayload.respondingTo.

92-95: Translation fallback wrapper duplicates i18next's own fallback mechanism.

This helper papers over missing keys at the call site by detecting message === key and substituting a hardcoded English fallback. That makes missing translations invisible (no warnings in dev), and the literal English fallback strings drift away from the canonical text in src/translations/en.json. Prefer relying on i18next's fallbackLng/default-namespace defaults (or t(key, { defaultValue: '…' })) so missing keys are reported through the standard channel and the canonical text lives in one place.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/stores/status/personnel-status-store.ts` around lines 92 - 95, The
getTranslatedMessage wrapper duplicates i18next fallback behavior and hides
missing-key warnings; remove or stop using getTranslatedMessage and instead call
translate directly with i18next's built-in fallback/defaultValue behavior (e.g.,
translate(key, { defaultValue: fallback })) or rely on the configured
fallbackLng/default namespace so missing keys surface normally; update all call
sites that use getTranslatedMessage to use translate(key, { defaultValue:
fallback }) or just translate(key) and ensure the canonical English text remains
only in the translations source.
src/lib/status-destinations.ts (1)

33-45: Two POI helpers with confusing names invite the mix-up already happening in the store.

arePoisAllowedForDetail and arePoisAllowedForStatus differ only by a legacy fallback, but the names give no hint about which is the strict one. As a result personnel-status-store.ts:setSelectedTab uses the lenient version while getAllowedDestinationTabsForDetail (right below, line 58) uses the strict version, producing divergent answers for legacy detail values like Call (2). Consider renaming for clarity (e.g., arePoisStrictlyAllowedForDetail / arePoisAllowedForDetailWithLegacyFallback) and picking a single one to drive both tab rendering and tab gating, or fold the legacy fallback into a single function and use it everywhere.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/status-destinations.ts` around lines 33 - 45, The two helpers are
confusing: arePoisAllowedForDetail (strict) and arePoisAllowedForStatus (lenient
with legacy fallback) cause inconsistent behavior; consolidate them into a
single clearly named helper (e.g., arePoisAllowedForDetailWithLegacyFallback or
arePoisStrictlyAllowedForDetail and a separate legacy wrapper) and update call
sites so both personnel-status-store:setSelectedTab and
getAllowedDestinationTabsForDetail use the same helper; rename the original
functions (or replace one) and update all imports/usages to the chosen function
name, ensuring the legacy fallback logic (the
areCallsAllowedForDetail/areStationsAllowedForDetail checks) is either folded
into the single helper or explicitly separated and used consistently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ffa3a1c9-b7ee-4339-af14-ead2574f6f22

📥 Commits

Reviewing files that changed from the base of the PR and between 655d335 and fdb3d19.

📒 Files selected for processing (59)
  • src/api/calls/calls.ts
  • src/api/dispatch/index.ts
  • src/api/mapping/mapping.ts
  • src/app/(app)/__tests__/map.test.tsx
  • src/app/(app)/map.tsx
  • src/app/_layout.tsx
  • src/app/call/[id]/edit.tsx
  • src/app/call/[id]/index.tsx
  • src/app/call/new/index.tsx
  • src/app/poi/[id].tsx
  • src/components/home/__tests__/status-buttons.test.tsx
  • src/components/home/status-buttons.tsx
  • src/components/home/user-status-card.tsx
  • src/components/maps/map-panel.tsx
  • src/components/maps/map-pins.tsx
  • src/components/maps/pin-detail-modal.tsx
  • src/components/maps/pin-marker.tsx
  • src/components/maps/poi-list-panel.tsx
  • src/components/status/__tests__/personnel-status-bottom-sheet.test.tsx
  • src/components/status/personnel-status-bottom-sheet.tsx
  • src/components/status/store.ts
  • src/components/ui/shared-tabs.tsx
  • src/components/units/unit-card.tsx
  • src/components/units/unit-details-sheet.tsx
  • src/constants/map-icons.ts
  • src/lib/poi.ts
  • src/lib/status-destinations.ts
  • src/models/offline-queue/queued-event.ts
  • src/models/v4/calls/callResultData.ts
  • src/models/v4/dispatch/getSetUnitStateResultData.ts
  • src/models/v4/dispatch/newCallFormResultData.ts
  • src/models/v4/groups/groupsResultData.ts
  • src/models/v4/mapping/getMapDataAndMarkersData.ts
  • src/models/v4/mapping/poiLayerData.ts
  • src/models/v4/mapping/poiResult.ts
  • src/models/v4/mapping/poiResultData.ts
  • src/models/v4/mapping/poiTypeResultData.ts
  • src/models/v4/mapping/poiTypesResult.ts
  • src/models/v4/mapping/poisResult.ts
  • src/models/v4/personnelStatuses/getCurrentStatusResultData.ts
  • src/models/v4/personnelStatuses/savePersonStatusInput.ts
  • src/models/v4/personnelStatuses/savePersonsStatusesInput.ts
  • src/models/v4/statuses/customStateDetailTypes.ts
  • src/models/v4/statuses/destinationEntityTypes.ts
  • src/models/v4/unitStatus/saveUnitStatusInput.ts
  • src/models/v4/unitStatus/unitStatusResultData.ts
  • src/services/offline-event-manager.service.ts
  • src/stores/poi/store.ts
  • src/stores/status/__tests__/personnel-status-store.test.ts
  • src/stores/status/personnel-status-store.ts
  • src/translations/ar.json
  • src/translations/de.json
  • src/translations/en.json
  • src/translations/es.json
  • src/translations/fr.json
  • src/translations/it.json
  • src/translations/pl.json
  • src/translations/sv.json
  • src/translations/uk.json

Comment thread src/app/(app)/map.tsx
Comment thread src/app/call/new/index.tsx Outdated
Comment thread src/app/poi/[id].tsx
Comment thread src/components/maps/pin-detail-modal.tsx Outdated
Comment thread src/components/status/personnel-status-bottom-sheet.tsx Outdated
Comment thread src/stores/poi/store.ts Outdated
Comment thread src/stores/status/personnel-status-store.ts
Comment thread src/translations/de.json Outdated
Comment thread src/translations/en.json Outdated
"loading_detail": "Loading POI details...",
"not_found": "POI not found",
"note_label": "Note",
"results_count": "{{count}} POIs",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use pluralization keys for POI result counts.

results_count is fixed to plural text, so singular values render incorrectly (for example, “1 POIs”). Add singular/plural variants and keep locale files aligned to the same key pattern.

💡 Suggested change
-    "results_count": "{{count}} POIs",
+    "results_count": "{{count}} POI",
+    "results_count_plural": "{{count}} POIs",
📝 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
"results_count": "{{count}} POIs",
"results_count": "{{count}} POI",
"results_count_plural": "{{count}} POIs",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/translations/en.json` at line 847, The current translation key
"results_count" is hardcoded to the plural form; change it to provide singular
and plural variants (e.g., replace "results_count": "{{count}} POIs" with a
pluralized entry such as "results_count": { "one": "{{count}} POI", "other":
"{{count}} POIs" } or use your project's ICU plural syntax) and update all
locale files to use the same key shape so the runtime pluralization logic (the
code that looks up "results_count") will render "1 POI" vs "2 POIs" correctly.

Comment thread src/translations/it.json
Comment on lines +841 to +857
"list_description": "Sfoglia i POI del dipartimento, filtra per tipo e torna alla mappa.",
"load_error_title": "Impossibile caricare il POI",
"loading": "Caricamento POI...",
"loading_detail": "Caricamento dettagli POI...",
"not_found": "POI non trovato",
"note_label": "Nota",
"results_count": "{{count}} POI",
"route_error": "Impossibile aprire l'app di mappe",
"set_status_destination": "Usa come destinazione dello stato",
"sort_label": "Ordina",
"sort_name": "Nome",
"sort_type": "Tipo",
"title": "POI",
"type_label": "Tipo",
"view_details": "Visualizza dettagli",
"view_on_map": "Mostra sulla mappa",
"view_on_map_hint": "Tieni aperta la mappa per vedere insieme la tua posizione e il POI selezionato."
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Italian register inconsistency in new POI strings.

The rest of it.json consistently uses the formal/infinitive register (e.g., "Selezionare", "Inserire", "È sicuro/a", "Aggiungere"). Several new POI entries switch to the informal tu form, which will read inconsistently to Italian users:

  • Line 841 list_description: "Sfoglia i POI..." → prefer "Sfogliare i POI..."
  • Line 849 set_status_destination: "Usa come destinazione..." → prefer "Usare come destinazione..." (or "Utilizzare...")
  • Line 857 view_on_map_hint: "Tieni aperta la mappa... la tua posizione..." → prefer "Tenere aperta la mappa... la propria posizione..."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/translations/it.json` around lines 841 - 857, Update the three POI
translation keys to use the formal/infinitive Italian register to match the rest
of it.json: change "list_description" from "Sfoglia i POI del dipartimento,
filtra per tipo e torna alla mappa." to "Sfogliare i POI del dipartimento,
filtrare per tipo e tornare alla mappa."; change "set_status_destination" from
"Usa come destinazione dello stato" to "Usare come destinazione dello stato" (or
"Utilizzare come destinazione dello stato"); and change "view_on_map_hint" from
"Tieni aperta la mappa per vedere insieme la tua posizione e il POI
selezionato." to "Tenere aperta la mappa per vedere insieme la propria posizione
e il POI selezionato." Ensure you update the values for the keys
list_description, set_status_destination, and view_on_map_hint accordingly.

Copy link
Copy Markdown

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/app/call/new/index.tsx (1)

1-35: ⚠️ Potential issue | 🟡 Minor

Fix the import block before merge.

CI is already failing simple-import-sort/imports on this file, so this import section needs an autofix/reorder pass before the PR can land.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/call/new/index.tsx` around lines 1 - 35, The top import block is
unsorted (CI failing simple-import-sort/imports); run the import auto-fix and
reorder the imports at the top of this file to match the project's import
groups/order (or run ESLint with the simple-import-sort autofix), and remove any
unused imports such as render and axios if they are not used; target the import
list containing symbols like zodResolver, useFocusEffect, render, axios,
Location, router, Stack, ChevronDownIcon, useColorScheme, React, Controller,
useForm, useTranslation, Platform, useSafeAreaInsets, z, createCall,
getNewCallData, DispatchSelectionModal, Loading, FullScreenLocationPicker,
logger, LocationPicker, CustomBottomSheet, Box, Button, Card, FormControl,
Input, Select, Text, Textarea, useAnalytics, useToast,
getDestinationPoiIdFromValue, and PoiResultData to ensure ordering and unused
imports are fixed.
♻️ Duplicate comments (1)
src/stores/status/personnel-status-store.ts (1)

111-152: ⚠️ Potential issue | 🟠 Major

Use one POI-allowance rule throughout the status flow.

getDestinationStateForStatus() and arePoisAllowed() still treat legacy call/station detail values as POI-capable via arePoisAllowedForStatus(), but setSelectedTab() only accepts 'pois' when arePoisAllowedForDetail() is true. That leaves a broken state where a preselected POI can open on the POIs tab, or the UI can expose POIs, but tapping back to that tab becomes a no-op.

Also applies to: 261-276, 440-442

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/stores/status/personnel-status-store.ts` around lines 111 - 152, The code
is using arePoisAllowedForStatus(...) in getDestinationStateForStatus (and
similarly at other locations) which permits POIs based on legacy rules, causing
mismatch with setSelectedTab() which uses arePoisAllowedForDetail(...); change
all uses of arePoisAllowedForStatus(selectedStatus.Detail) to
arePoisAllowedForDetail(selectedStatus.Detail) (including in
getDestinationStateForStatus, the other two similar blocks referenced, and any
place that checks POI allowance before setting selectedTab) so POI allowance is
derived from the same arePoisAllowedForDetail rule as setSelectedTab and avoid
the broken preselected-POI/tab no-op state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/stores/status/personnel-status-store.ts`:
- Around line 292-308: The fetchDestinationPois catch currently wipes
poiTypes/pois and hides loading, making failures look like “no POIs”; instead,
preserve existing poiTypes/pois (do not clear them) and set an explicit error
state (e.g., poisError: true or poisError: error message) so the UI can
distinguish network errors from an empty result; update the catch in
fetchDestinationPois to set { isLoadingPois: false, poisError: error } (or
poisError: true) and avoid resetting poiTypes/pois, and ensure the try branch
clears poisError on success; reference the fetchDestinationPois function and the
getPoiTypes/getPois calls when making this change.
- Around line 389-391: The QueuedPersonnelStatusEvent data interface is missing
the respondingToType field; update the QueuedPersonnelStatusEvent interface in
src/models/offline-queue/queued-event.ts to include respondingToType?: number |
null so it mirrors QueuedUnitStatusEvent and preserves RespondingToType when
queuing SavePersonStatusInput (this ensures offline-event-manager.service.ts can
read event.data.respondingToType during replay and the entity type isn't lost).

In `@src/translations/fr.json`:
- Around line 274-276: Add the missing French translation for the shared key
used by the destination picker: define "common": { "none": "Aucun" } (or another
appropriate French label) inside fr.json so that t('common.none') resolves to a
proper string instead of the raw key; update the locale object in fr.json to
include the common.none entry alongside existing keys like "destination",
"destination_load_error", and "destination_placeholder".

---

Outside diff comments:
In `@src/app/call/new/index.tsx`:
- Around line 1-35: The top import block is unsorted (CI failing
simple-import-sort/imports); run the import auto-fix and reorder the imports at
the top of this file to match the project's import groups/order (or run ESLint
with the simple-import-sort autofix), and remove any unused imports such as
render and axios if they are not used; target the import list containing symbols
like zodResolver, useFocusEffect, render, axios, Location, router, Stack,
ChevronDownIcon, useColorScheme, React, Controller, useForm, useTranslation,
Platform, useSafeAreaInsets, z, createCall, getNewCallData,
DispatchSelectionModal, Loading, FullScreenLocationPicker, logger,
LocationPicker, CustomBottomSheet, Box, Button, Card, FormControl, Input,
Select, Text, Textarea, useAnalytics, useToast, getDestinationPoiIdFromValue,
and PoiResultData to ensure ordering and unused imports are fixed.

---

Duplicate comments:
In `@src/stores/status/personnel-status-store.ts`:
- Around line 111-152: The code is using arePoisAllowedForStatus(...) in
getDestinationStateForStatus (and similarly at other locations) which permits
POIs based on legacy rules, causing mismatch with setSelectedTab() which uses
arePoisAllowedForDetail(...); change all uses of
arePoisAllowedForStatus(selectedStatus.Detail) to
arePoisAllowedForDetail(selectedStatus.Detail) (including in
getDestinationStateForStatus, the other two similar blocks referenced, and any
place that checks POI allowance before setting selectedTab) so POI allowance is
derived from the same arePoisAllowedForDetail rule as setSelectedTab and avoid
the broken preselected-POI/tab no-op state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e9aee466-7e22-432a-81fd-96883c260972

📥 Commits

Reviewing files that changed from the base of the PR and between fdb3d19 and 67bc77e.

📒 Files selected for processing (18)
  • src/app/(app)/map.tsx
  • src/app/call/[id]/edit.tsx
  • src/app/call/new/index.tsx
  • src/app/poi/[id].tsx
  • src/components/status/personnel-status-bottom-sheet.tsx
  • src/lib/status-destinations.ts
  • src/models/v4/unitStatus/unitStatusResultData.ts
  • src/stores/poi/store.ts
  • src/stores/status/personnel-status-store.ts
  • src/translations/ar.json
  • src/translations/de.json
  • src/translations/en.json
  • src/translations/es.json
  • src/translations/fr.json
  • src/translations/it.json
  • src/translations/pl.json
  • src/translations/sv.json
  • src/translations/uk.json
🚧 Files skipped from review as they are similar to previous changes (10)
  • src/app/poi/[id].tsx
  • src/translations/it.json
  • src/translations/pl.json
  • src/translations/ar.json
  • src/translations/de.json
  • src/models/v4/unitStatus/unitStatusResultData.ts
  • src/translations/uk.json
  • src/translations/es.json
  • src/translations/en.json
  • src/translations/sv.json

Comment thread src/stores/status/personnel-status-store.ts
Comment thread src/stores/status/personnel-status-store.ts
Comment thread src/translations/fr.json
@ucswift
Copy link
Copy Markdown
Member Author

ucswift commented Apr 27, 2026

Approve

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

This PR is approved.

@ucswift ucswift merged commit 2ef376e into master Apr 27, 2026
13 of 14 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request May 6, 2026
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.

1 participant