Skip to content

Syncing#94

Merged
ucswift merged 20 commits intodevelopfrom
master
Jan 23, 2026
Merged

Syncing#94
ucswift merged 20 commits intodevelopfrom
master

Conversation

@ucswift
Copy link
Member

@ucswift ucswift commented Jan 23, 2026

Summary by CodeRabbit

  • New Features

    • Enhanced image selection with platform-aware handling and improved error messaging for calls.
  • Bug Fixes

    • Fixed unit status display logic to prioritize custom statuses correctly.
    • Corrected personnel group filtering to include only station groups.
  • UI/UX Improvements

    • Improved audio stream dropdown to display selected value clearly.
    • Enhanced shift calendar layout for better content visibility.
    • Refined shift details sheet with improved scrolling and spacing.
  • Documentation

    • Added new localization strings for error messages and shift calendar labels across supported languages.

✏️ Tip: You can customize this high-level summary in your review settings.

ucswift added 20 commits August 23, 2025 16:24
CU-868ex18rd Fix for splash screen
CU-868ex18rd Splash screen changes.
CU-868ex18rd Working on geolocation issue minor fixes.
CU-868ex18rd And16k SignalR update.
CU-868ex18rd Expo 53 update.
CU-868ex18rd Fixing internal test builds
CU-868ex18rd Fix for ad hoc build
CU-868ex18rd Fixing the call images view.
CU-868ex18rd Fixing missing analytic key env.
CU-868ex18rd Fixing map issue.
CU-868ex18rd Migrating to countly
CU-868frkzum Fixed issue with broken onboarding screens.
RR-T39 Added skip button to onboarding page top and scroll.
RR-T39 Fixing the ci build
RR-T40 Logout fix, close call fix, units state fix, contacts fix, pro…
@coderabbitai
Copy link

coderabbitai bot commented Jan 23, 2026

📝 Walkthrough

Walkthrough

Multiple enhancements across image handling, shift day filtering, and status logic. Cross-platform image picker/upload refactoring with permission handling and base64 encoding. Shift components now filter upcoming days, apply layout constraints, and use translations. Personnel status store filters groups by type. Minor UI and selector improvements.

Changes

Cohort / File(s) Summary
Image Handling & Permissions
src/components/calls/call-images-modal.tsx
Refactored image picker and camera capture flows with cross-platform support: Web skips permissions; native requests with settings handling. Added base64 encoding for uploads via ImageManipulator with fallbacks. Validates asset URIs, handles Android pending results, and provides contextual error toasts.
Shift Component UI & Filtering
src/components/shifts/shift-details-sheet.tsx, src/components/shifts/shift-day-details-sheet.tsx
Filters shift days to exclude past dates, limits to 7 upcoming days with translation. Updates bottom sheet snapPoints (80→90), adds flex constraints, and adjusts ScrollView padding. Enhanced vertical scrollbar visibility and content growth.
Shift Translations & Layout
src/components/shifts/shift-calendar-view.tsx, src/components/calendar/calendar-item-details-sheet.tsx
Replaces hard-coded legend text with i18n translations. Converts fixed height policy (80%) to flexible flex: 1 layout with contentContainerStyle flexGrow.
Shift Tests
src/components/shifts/__tests__/shift-details-sheet.test.tsx
Renames test suite from "Recent" to "Upcoming" Shift Days. Updates mock dates to 2027, adds future-dated shift data. Adjusts ScrollView assertions (showsVerticalScrollIndicator false→true, paddingBottom 20→40). Adds startOfDay mock for date filtering.
Personnel Status & Group Filtering
src/stores/status/personnel-status-store.ts, src/stores/status/__tests__/personnel-status-store.test.ts
Filters groups to include only TypeId === 1 (station groups), excluding others. Test updated to verify filtered group set.
Unit Card Status Logic
src/components/units/unit-card.tsx
Prioritizes custom status text and color; falls back to CurrentStatus/CurrentStatusColor from unit data. Removes default status text coupling to API response.
Audio Stream Selector
src/components/audio-stream/audio-stream-bottom-sheet.tsx
Adds value prop to SelectInput to display current stream name or "none" label.
Localization
src/translations/ar.json, src/translations/en.json, src/translations/es.json
Adds new keys: callImages (capture_error, permission_denied_settings, select_error), shifts (assigned, legend, upcoming_shift_days). Spanish file includes updated callImages.image_note and callImages.no_images_description.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as Image Selection UI
    participant Permissions as Permissions Handler
    participant Picker as ImagePicker
    participant Manipulator as ImageManipulator
    participant FileSystem as FileSystem
    participant API as Upload API

    User->>UI: Select/Capture Image
    UI->>Permissions: Check platform & request
    alt Web
        Permissions-->>UI: Skip permissions
    else Native
        Permissions->>Permissions: Request camera/library access
        alt Permission Granted
            Permissions-->>UI: Proceed
        else Permission Denied
            Permissions-->>UI: Show settings/error toast
        end
    end

    UI->>Picker: Launch image picker/camera
    Picker-->>UI: Return image asset with URI

    UI->>UI: Validate asset.uri
    alt URI Valid
        UI->>Manipulator: Manipulate & encode to base64
        Manipulator-->>UI: Return base64
        alt No base64 returned
            UI->>FileSystem: Read from manipulated URI
            FileSystem-->>UI: Return base64 via FileSystem
        end
        UI->>API: Upload base64 + metadata
        API-->>User: Success confirmation
    else URI Invalid
        UI-->>User: Show error toast & validation msg
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • CU-868ex18rd Fixing the call images view. #72: Modifies src/components/calls/call-images-modal.tsx with overlapping image manipulation, encoding, and translation key implementations.
  • Develop #77: Updates src/components/calls/call-images-modal.tsx UI/listing and image handling paths alongside callImages translation key adjustments.
  • Develop #84: Changes src/components/audio-stream/audio-stream-bottom-sheet.tsx; this PR adds value prop to SelectInput while related PR adjusts SelectContent styling.

Poem

🐰✨ Hop along as shifts align,
With days ahead that brightly shine!
Images fly through Web and native lands,
Status blooms from custom hands—
All translated, tested, ready fine! 📸🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Syncing' is vague and generic, providing no meaningful information about the changeset. Consider a more descriptive title that reflects the actual changes, such as 'Add cross-platform image handling and shift UI improvements' or reference the issue 'RR-T40: Fix test issues and enhance components'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

@ucswift
Copy link
Member Author

ucswift commented Jan 23, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 23, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

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

🤖 Fix all issues with AI agents
In `@src/components/shifts/shift-details-sheet.tsx`:
- Around line 260-280: Precompute the upcomingDays array by filtering
selectedShift.Days (using the existing parseISO/startOfDay logic and same
try/catch) into a const upcomingDays and then use a ternary to render the whole
"Upcoming Days" VStack only when upcomingDays.length > 0; replace the inline
filter+slice+map with upcomingDays.slice(0,7).map(d => <ShiftDayCard
key={d.ShiftDayId} shiftDay={d} onPress={() => selectShiftDay(d)} />) so the
header and list are omitted when the filtered list is empty.

In `@src/components/units/unit-card.tsx`:
- Around line 92-97: statusText currently only uses customStatusData?.Text or
unit.CurrentStatus and misses falling back to defaultStatus.text (and uses ||
which lets empty strings through); update the statusText calculation to:
prioritize customStatusData?.Text, then unit.CurrentStatus, then
defaultStatus?.text, and use the nullish coalescing operator (??) instead of ||
so empty strings from the API don't bypass the default. Keep the related logic
for buttonColor unchanged except ensure consistent use of ?? when reading
customStatusData?.BColor and unit.CurrentStatusColor to mirror the defensive
behavior used for statusText; reference variables/functions: statusText,
buttonColor, defaultStatus, getDefaultStatus, statusId, customStatusData, unit.
🧹 Nitpick comments (5)
src/stores/status/personnel-status-store.ts (1)

113-115: LGTM! The filtering logic is correct.

The change properly filters groups to only include station groups (TypeId === 1), with appropriate null handling via the || [] fallback.

Consider extracting the magic number 1 into a named constant (e.g., GROUP_TYPE_STATION = 1) for improved readability and maintainability, especially if this type ID is used elsewhere in the codebase.

src/components/audio-stream/audio-stream-bottom-sheet.tsx (1)

141-145: Placeholder prop is now redundant.

Since value is always provided (either currentStream.Name or the translated "none" string), the placeholder prop will never be displayed. Consider removing it to avoid confusion.

Suggested change
 <Select selectedValue={getCurrentStreamValue()} onValueChange={handleStreamSelection} isDisabled={isLoading || isBuffering}>
   <SelectTrigger>
-    <SelectInput placeholder={t('audio_streams.select_placeholder')} value={currentStream ? currentStream.Name : t('audio_streams.none')} />
+    <SelectInput value={currentStream ? currentStream.Name : t('audio_streams.none')} />
     <SelectIcon />
   </SelectTrigger>
src/components/shifts/__tests__/shift-details-sheet.test.tsx (2)

51-78: Mocked format date output is inconsistent with test data.

The format mock returns 'Jan 15, 2024' and '2024-01-15' while the test data uses 2027 dates. Although this works because mocks are static, it reduces test readability and could cause confusion when debugging.

Consider updating the mock to return dates consistent with the test data:

🔧 Suggested improvement
 jest.mock('date-fns', () => ({
   format: jest.fn((date, formatStr) => {
     if (formatStr === 'MMM dd, yyyy') {
-      return 'Jan 15, 2024';
+      return 'Jan 15, 2027';
     }
     if (formatStr === 'yyyy-MM-dd') {
-      return '2024-01-15';
+      return '2027-01-15';
     }
     return date;
   }),

916-917: Remove empty duplicate describe block.

This empty describe('Interaction', ...) block is redundant since there's already a populated describe('Interaction', ...) block at lines 659-711. Remove this dead code.

🧹 Proposed fix
-  describe('Interaction', () => {
-  });
-
   describe('Calendar Tab', () => {
src/components/calls/call-images-modal.tsx (1)

280-293: Handle blob: URIs in the web fallback for future compatibility.

The project currently uses expo-image-picker ~16.1.4, which returns data: URIs on web. However, expo-image-picker 17.0.0 and later (released Aug 13, 2025) returns blob: URLs instead as a breaking change. When upgrading, the fallback catch block (lines 284–293) will fail to extract base64 from blob URLs, causing uploads to fail. Even though the code comment acknowledges blob URL possibility (line 282), the implementation doesn't handle it. Add blob→base64 conversion to future-proof the error handler:

Suggested blob URL fallback
         } catch (webError) {
           console.error('Web image manipulation error:', webError);
           // On web, the URI might already be a data URI or blob URL
           // Try to extract base64 from data URI
           if (selectedImageInfo.uri.startsWith('data:')) {
             const base64Match = selectedImageInfo.uri.match(/base64,(.*)$/);
             if (base64Match && base64Match[1]) {
               base64Image = base64Match[1];
             } else {
               throw new Error('Could not extract base64 from data URI');
             }
+          } else if (selectedImageInfo.uri.startsWith('blob:')) {
+            const response = await fetch(selectedImageInfo.uri);
+            const blob = await response.blob();
+            const dataUrl = await new Promise<string>((resolve, reject) => {
+              const reader = new FileReader();
+              reader.onloadend = () => {
+                if (typeof reader.result === 'string') {
+                  resolve(reader.result);
+                } else {
+                  reject(new Error('Unexpected FileReader result'));
+                }
+              };
+              reader.onerror = () => reject(reader.error ?? new Error('FileReader error'));
+              reader.readAsDataURL(blob);
+            });
+            const base64Match = dataUrl.match(/base64,(.*)$/);
+            if (base64Match && base64Match[1]) {
+              base64Image = base64Match[1];
+            } else {
+              throw new Error('Could not extract base64 from blob data URL');
+            }
           } else {
             throw webError;
           }
         }

Comment on lines +260 to +280
{/* Upcoming Days */}
{selectedShift?.Days && selectedShift.Days.length > 0 && (
<VStack space="md">
<Text size="lg" className="font-semibold text-gray-900 dark:text-white">
Recent Shift Days
{t('shifts.upcoming_shift_days')}
</Text>
<VStack space="sm">
{selectedShift.Days.slice(0, 7).map((day) => (
<ShiftDayCard key={day.ShiftDayId} shiftDay={day} onPress={() => selectShiftDay(day)} />
))}
{selectedShift.Days.filter((day) => {
if (!day.ShiftDay) return false;
try {
const shiftDate = startOfDay(parseISO(day.ShiftDay));
const today = startOfDay(new Date());
return shiftDate >= today;
} catch {
return false;
}
})
.slice(0, 7)
.map((day) => (
<ShiftDayCard key={day.ShiftDayId} shiftDay={day} onPress={() => selectShiftDay(day)} />
))}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid rendering the “Upcoming Shift Days” section when the filtered list is empty.
If all days are filtered out (e.g., only past dates), the header still shows with no items. Precompute the filtered list once and guard the section. As per coding guidelines, prefer ternary rendering.

🔧 Suggested fix
-          {/* Upcoming Days */}
-          {selectedShift?.Days && selectedShift.Days.length > 0 && (
-            <VStack space="md">
-              <Text size="lg" className="font-semibold text-gray-900 dark:text-white">
-                {t('shifts.upcoming_shift_days')}
-              </Text>
-              <VStack space="sm">
-                {selectedShift.Days.filter((day) => {
-                  if (!day.ShiftDay) return false;
-                  try {
-                    const shiftDate = startOfDay(parseISO(day.ShiftDay));
-                    const today = startOfDay(new Date());
-                    return shiftDate >= today;
-                  } catch {
-                    return false;
-                  }
-                })
-                  .slice(0, 7)
-                  .map((day) => (
-                    <ShiftDayCard key={day.ShiftDayId} shiftDay={day} onPress={() => selectShiftDay(day)} />
-                  ))}
-              </VStack>
-            </VStack>
-          )}
+          const today = startOfDay(new Date());
+          const upcomingDays =
+            selectedShift?.Days?.filter((day) => {
+              if (!day.ShiftDay) return false;
+              try {
+                const shiftDate = startOfDay(parseISO(day.ShiftDay));
+                return shiftDate >= today;
+              } catch {
+                return false;
+              }
+            }).slice(0, 7) ?? [];
+
+          {/* Upcoming Days */}
+          {upcomingDays.length > 0 ? (
+            <VStack space="md">
+              <Text size="lg" className="font-semibold text-gray-900 dark:text-white">
+                {t('shifts.upcoming_shift_days')}
+              </Text>
+              <VStack space="sm">
+                {upcomingDays.map((day) => (
+                  <ShiftDayCard key={day.ShiftDayId} shiftDay={day} onPress={() => selectShiftDay(day)} />
+                ))}
+              </VStack>
+            </VStack>
+          ) : null}
As per coding guidelines, prefer ternary rendering.
🤖 Prompt for AI Agents
In `@src/components/shifts/shift-details-sheet.tsx` around lines 260 - 280,
Precompute the upcomingDays array by filtering selectedShift.Days (using the
existing parseISO/startOfDay logic and same try/catch) into a const upcomingDays
and then use a ternary to render the whole "Upcoming Days" VStack only when
upcomingDays.length > 0; replace the inline filter+slice+map with
upcomingDays.slice(0,7).map(d => <ShiftDayCard key={d.ShiftDayId} shiftDay={d}
onPress={() => selectShiftDay(d)} />) so the header and list are omitted when
the filtered list is empty.

Comment on lines +92 to +97
// Fall back to default status if custom status not found (used for colors only)
const defaultStatus = statusId && !customStatusData ? getDefaultStatus(statusId) : null;

// Get status text and colors - prioritize custom, then default, then API response
const statusText = customStatusData?.Text || (defaultStatus?.text ? t(defaultStatus.text) : null) || ('CurrentStatus' in unit ? unit.CurrentStatus : null);
const buttonColor = customStatusData?.BColor || defaultStatus?.color || ('CurrentStatusColor' in unit ? unit.CurrentStatusColor : null);
// Get status text - prioritize custom status, then API response (CurrentStatus from unit)
const statusText = customStatusData?.Text || ('CurrentStatus' in unit ? unit.CurrentStatus : null);
const buttonColor = customStatusData?.BColor || ('CurrentStatusColor' in unit ? unit.CurrentStatusColor : null) || defaultStatus?.color;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n src/components/units/unit-card.tsx

Repository: Resgrid/Responder

Length of output: 8166


🏁 Script executed:

find src -type f -name "*.ts" | xargs grep -l "StatusesResultData\|UnitResultData\|UnitInfoResultData" | head -5

Repository: Resgrid/Responder

Length of output: 276


🏁 Script executed:

cat -n src/models/v4/statuses/statusesResultData.ts

Repository: Resgrid/Responder

Length of output: 565


🏁 Script executed:

cat -n src/models/v4/units/unitResultData.ts

Repository: Resgrid/Responder

Length of output: 855


🏁 Script executed:

cat -n src/models/v4/units/unitInfoResultData.ts

Repository: Resgrid/Responder

Length of output: 1119


Guard against missing status text when CurrentStatus is unavailable.

The status badge will not render if the API omits CurrentStatus and no custom status is set for the unit type, even though a valid default status exists for the statusId. Additionally, statusText lacks the fallback to defaultStatus.text that buttonColor has, creating an asymmetry. Using ?? instead of || is also safer to avoid empty-string pass-through from the API.

Suggested fix
-  const statusText = customStatusData?.Text || ('CurrentStatus' in unit ? unit.CurrentStatus : null);
+  const statusText =
+    customStatusData?.Text ??
+    ('CurrentStatus' in unit ? unit.CurrentStatus : null) ??
+    (defaultStatus ? t(defaultStatus.text) : null);
🤖 Prompt for AI Agents
In `@src/components/units/unit-card.tsx` around lines 92 - 97, statusText
currently only uses customStatusData?.Text or unit.CurrentStatus and misses
falling back to defaultStatus.text (and uses || which lets empty strings
through); update the statusText calculation to: prioritize
customStatusData?.Text, then unit.CurrentStatus, then defaultStatus?.text, and
use the nullish coalescing operator (??) instead of || so empty strings from the
API don't bypass the default. Keep the related logic for buttonColor unchanged
except ensure consistent use of ?? when reading customStatusData?.BColor and
unit.CurrentStatusColor to mirror the defensive behavior used for statusText;
reference variables/functions: statusText, buttonColor, defaultStatus,
getDefaultStatus, statusId, customStatusData, unit.

@ucswift ucswift merged commit 619bd89 into develop Jan 23, 2026
15 of 16 checks passed
ucswift added a commit that referenced this pull request Jan 23, 2026
Merge pull request #94 from Resgrid/master
This was referenced Jan 27, 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