Skip to content

Develop#100

Merged
ucswift merged 3 commits intomasterfrom
develop
Jan 27, 2026
Merged

Develop#100
ucswift merged 3 commits intomasterfrom
develop

Conversation

@ucswift
Copy link
Member

@ucswift ucswift commented Jan 27, 2026

Summary by CodeRabbit

  • New Features

    • Image picker support with configurable camera/photos permissions.
    • Avatar everywhere with intelligent initials and deterministic colors; avatar shown in personnel details header.
    • Android microphone permission checks before joining calls/foreground service.
    • New utilities for safe timestamp formatting, initials, and color generation.
  • Bug Fixes

    • Improved avatar image error handling and fallback rendering.
    • Corrected shift type label translation.
  • Tests

    • Added tests/mocks covering new utilities and avatar behavior.

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

@coderabbitai
Copy link

coderabbitai bot commented Jan 27, 2026

📝 Walkthrough

Walkthrough

This PR adds avatar rendering utilities and avatar-based UI (with initials fallback), introduces safe timestamp formatting, configures expo-image-picker permissions, adds Android microphone permission checks before LiveKit connection/foreground service start, fixes a shift translation key, and expands utils tests/mocks.

Changes

Cohort / File(s) Summary
Configuration
app.config.ts
Added expo-image-picker plugin block with cameraPermission and photosPermission entries.
Personnel UI
src/components/personnel/personnel-card.tsx, src/components/personnel/personnel-details-sheet.tsx
Reworked header to avatar-centric layout; added Avatar component usage with AvatarImage and initials fallback; introduced imageError state handling; use getAvatarUrl, getColorFromString, getInitials, and safeFormatTimestamp; updated role badge rendering and timestamp display.
Shifts UI
src/components/shifts/shift-details-sheet.tsx
Corrected translation key from shifts.shift_type to shifts.shift_type.label.
Permissions & LiveKit
src/stores/app/livekit-store.ts
Added Android-specific microphone permission checks (call to getRecordingPermissionsAsync / requestRecordingPermissionsAsync) before connecting to LiveKit and starting foreground service; added logging and error handling when denied.
Utilities
src/lib/utils.ts, src/lib/__tests__/utils.test.ts
Added and exported safeFormatTimestamp, getColorFromString, and getInitials; added tests for new utilities.
Tests / Mocks
src/components/personnel/__tests__/personnel-details-sheet.test.tsx
Extended mock for @/lib/utils to stub safeFormatTimestamp, getAvatarUrl, getInitials, and getColorFromString.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant UI as UI Component
  participant Permissions as Android Permissions
  participant LiveKit as LiveKit Client
  participant FG as Foreground Service

  rect rgba(220,240,255,0.5)
    UI->>Permissions: Check microphone permission (android only)
    alt permission not granted
      Permissions->>Permissions: requestRecordingPermissionsAsync()
    end
    Permissions-->>UI: permission result (granted/denied)
  end

  alt granted
    UI->>LiveKit: disconnect existing room (if any)
    UI->>LiveKit: connectToRoom()
    UI->>FG: start foreground service
  else denied
    UI-->>UI: throw / log error (permission denied)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • RU-T46 Bug fixed from testing #96 — modifies LiveKit store connection/ microphone lifecycle; closely related to the Android mic-permission checks here.
  • Syncing #94 — changes shift details UI/translation handling; related to the shift translation key adjustment in this PR.

Poem

🐰
In burrows bright my pixels play,
Avatars bloom when photos stray,
Initials bounce in colored cheer,
Android whispers, "Mic's clear!"
Hoppity-hop — the app greets near. 🎨📱

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Develop' is extremely vague and generic, providing no meaningful information about the changeset contents. Replace with a descriptive title summarizing the main changes, such as 'Add avatar UI and permission handling for personnel components and LiveKit' or 'Implement avatar display and Android microphone permissions'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

✏️ 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.

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/personnel/personnel-card.tsx`:
- Around line 45-52: The PersonnelCard component retains imageError across prop
changes causing stale fallbacks; add a useEffect in PersonnelCard that watches
the personnel identity (e.g., personnel.UserId or the personnel object) and
calls setImageError(false) whenever it changes so a new avatar load is attempted
for each new person; update the effect to run on mount and on personnel changes
to reset the state used by avatarUrl/initials/fallbackColor.
- Line 148: This component directly calls parseDateISOString and
formatDateForDisplay on personnel.StatusTimestamp which can throw on malformed
input; replace that inline parsing with the existing safeFormatTimestamp
behavior (or extract/reuse the shared safeFormatTimestamp utility used in
PersonnelDetailsSheet) so the formatting is done inside a try/catch and returns
a safe string/null; update the render to use
safeFormatTimestamp(personnel.StatusTimestamp) (or the newly extracted helper)
instead of parseDateISOString/formatDateForDisplay to prevent unhandled
exceptions.
🧹 Nitpick comments (3)
src/components/personnel/personnel-details-sheet.tsx (1)

22-43: Extract shared helper functions to a utility module.

getColorFromString and getInitials are duplicated in both personnel-details-sheet.tsx and personnel-card.tsx. Consider extracting these to @/lib/utils.ts alongside getAvatarUrl to maintain DRY principles and ensure consistent behavior across components.

♻️ Suggested refactor

Add to src/lib/utils.ts:

/**
 * Generates a deterministic color from a string (user ID or name)
 * Returns an HSL color string
 */
export function getColorFromString(str: string): string {
  let hash = 0;
  for (let i = 0; i < str.length; i++) {
    hash = str.charCodeAt(i) + ((hash << 5) - hash);
  }
  const hue = Math.abs(hash % 360);
  return `hsl(${hue}, 65%, 45%)`;
}

/**
 * Gets initials from first and last name
 */
export function getInitials(firstName?: string, lastName?: string): string {
  const first = firstName?.trim()?.[0]?.toUpperCase() || '';
  const last = lastName?.trim()?.[0]?.toUpperCase() || '';
  return first + last || '?';
}

Then import from both components:

import { getAvatarUrl, getColorFromString, getInitials } from '@/lib/utils';
src/components/personnel/personnel-card.tsx (2)

16-38: Duplicate helper functions - see personnel-details-sheet.tsx comment.

These helper functions should be extracted to @/lib/utils.ts as noted in the other file's review.


103-114: Redundant GroupName check.

Line 103 already verifies personnel.GroupName is truthy, making the nested check on line 105 redundant.

♻️ Proposed simplification
-            ) : personnel.GroupName ? (
-              <VStack space="xs">
-                {personnel.GroupName ? (
-                  <HStack space="xs" className="items-center">
-                    <Users size={16} className="text-gray-600 dark:text-gray-400" />
-                    <Text className="text-sm text-gray-600 dark:text-gray-300" numberOfLines={1}>
-                      {personnel.GroupName}
-                    </Text>
-                  </HStack>
-                ) : null}
-              </VStack>
+            ) : personnel.GroupName ? (
+              <VStack space="xs">
+                <HStack space="xs" className="items-center">
+                  <Users size={16} className="text-gray-600 dark:text-gray-400" />
+                  <Text className="text-sm text-gray-600 dark:text-gray-300" numberOfLines={1}>
+                    {personnel.GroupName}
+                  </Text>
+                </HStack>
+              </VStack>
             ) : null}

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: 1

🤖 Fix all issues with AI agents
In `@src/lib/__tests__/utils.test.ts`:
- Line 1: Add unit tests covering safeFormatTimestamp in
src/lib/__tests__/utils.test.ts: create cases for (1) valid timestamp inputs
with different format strings to assert expected outputs, (2) null and undefined
inputs to assert they return empty string or expected fallback, and (3)
invalid/non-parsable timestamp inputs to assert the function handles errors
gracefully (e.g., returns empty string or a specific error value). Use the
existing test harness and imports (safeFormatTimestamp) and mirror patterns used
for other helpers (formatLocalDateString, getTodayLocalString) to keep tests
consistent and isolated.
🧹 Nitpick comments (5)
src/components/personnel/personnel-details-sheet.tsx (2)

22-24: Remove extra blank lines.

Static analysis indicates unnecessary blank lines here. As per the pipeline failure hints.

♻️ Proposed fix
 import { VStack } from '../ui/vstack';
-
-
-
 
 export const PersonnelDetailsSheet: React.FC = () => {

100-110: Simplify JSX formatting for avatar components.

Static analysis suggests condensing the multi-line JSX to single lines for cleaner formatting.

♻️ Proposed fix
               <Avatar size="lg" style={imageError ? { backgroundColor: fallbackColor } : undefined}>
-                {!imageError && (
-                  <AvatarImage
-                    source={{ uri: avatarUrl }}
-                    onError={() => setImageError(true)}
-                  />
-                )}
-                {imageError && (
-                  <AvatarFallbackText className="text-white">{initials}</AvatarFallbackText>
-                )}
+                {!imageError ? (
+                  <AvatarImage source={{ uri: avatarUrl }} onError={() => setImageError(true)} />
+                ) : (
+                  <AvatarFallbackText className="text-white">{initials}</AvatarFallbackText>
+                )}
               </Avatar>

This also aligns with the coding guideline to use ternary operator ? : for conditional rendering instead of &&.

src/components/personnel/personnel-card.tsx (3)

16-17: Remove extra blank lines.

Static analysis indicates unnecessary blank lines here.

♻️ Proposed fix
 import { VStack } from '../ui/vstack';
-
-
 
 interface PersonnelCardProps {

41-51: Simplify JSX formatting and use ternary for conditional rendering.

Static analysis suggests condensing multi-line JSX. Additionally, per coding guidelines, use ternary operator ? : instead of && for conditional rendering.

♻️ Proposed fix
           <Avatar size="md" style={imageError ? { backgroundColor: fallbackColor } : undefined}>
-            {!imageError && (
-              <AvatarImage
-                source={{ uri: avatarUrl }}
-                onError={() => setImageError(true)}
-              />
-            )}
-            {imageError && (
-              <AvatarFallbackText className="text-white">{initials}</AvatarFallbackText>
-            )}
+            {!imageError ? (
+              <AvatarImage source={{ uri: avatarUrl }} onError={() => setImageError(true)} />
+            ) : (
+              <AvatarFallbackText className="text-white">{initials}</AvatarFallbackText>
+            )}
           </Avatar>

5-5: Remove unused imports formatDateForDisplay and parseDateISOString from the import statement on line 5.

These functions are imported but not used anywhere in the component. The component uses safeFormatTimestamp for timestamp formatting instead.

♻️ Proposed fix
-import { formatDateForDisplay, getAvatarUrl, getColorFromString, getInitials, parseDateISOString, safeFormatTimestamp } from '@/lib/utils';
+import { getAvatarUrl, getColorFromString, getInitials, safeFormatTimestamp } from '@/lib/utils';

@@ -1,4 +1,4 @@
import { formatLocalDateString, isSameDate, isToday, getTodayLocalString } from '../utils';
import { formatLocalDateString, isSameDate, isToday, getTodayLocalString, safeFormatTimestamp, getColorFromString, getInitials } from '../utils';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for safeFormatTimestamp tests in the test file
rg -n "safeFormatTimestamp" src/lib/__tests__/utils.test.ts -C 5

Repository: Resgrid/Responder

Length of output: 440


🏁 Script executed:

#!/bin/bash
# Also check the entire test file to see all test cases
wc -l src/lib/__tests__/utils.test.ts && echo "---" && cat -n src/lib/__tests__/utils.test.ts | tail -100

Repository: Resgrid/Responder

Length of output: 4075


🏁 Script executed:

#!/bin/bash
# Search for the utils implementation to understand what safeFormatTimestamp does
cat -n src/lib/utils.ts | grep -A 20 "safeFormatTimestamp"

Repository: Resgrid/Responder

Length of output: 877


Add test cases for safeFormatTimestamp.

The function is imported but has no test coverage. Add tests for various scenarios: valid timestamps with different formats, null/undefined inputs, and error handling for invalid timestamps.

🤖 Prompt for AI Agents
In `@src/lib/__tests__/utils.test.ts` at line 1, Add unit tests covering
safeFormatTimestamp in src/lib/__tests__/utils.test.ts: create cases for (1)
valid timestamp inputs with different format strings to assert expected outputs,
(2) null and undefined inputs to assert they return empty string or expected
fallback, and (3) invalid/non-parsable timestamp inputs to assert the function
handles errors gracefully (e.g., returns empty string or a specific error
value). Use the existing test harness and imports (safeFormatTimestamp) and
mirror patterns used for other helpers (formatLocalDateString,
getTodayLocalString) to keep tests consistent and isolated.

@ucswift
Copy link
Member Author

ucswift commented Jan 27, 2026

Approve

Copy link

@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 8a53e29 into master Jan 27, 2026
13 of 14 checks passed
This was referenced Jan 28, 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