Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions docs/map-theme-implementation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Map Theme Implementation

## Overview
This document outlines the implementation of light/dark theme support for the map component in the Resgrid Unit app.

## Changes Made

### 1. Map Component Updates (`src/app/(app)/index.tsx`)

#### Theme Integration
- Imported `useColorScheme` from `nativewind` for theme detection
- Added `getMapStyle()` function that returns appropriate Mapbox style based on current theme:
- **Light mode**: `Mapbox.StyleURL.Street` (professional, muted appearance)
- **Dark mode**: `Mapbox.StyleURL.Dark` (dark theme optimized for low light)

#### Dynamic Styling
- Created `getThemedStyles()` function for theme-aware component styling
- Updated marker and recenter button styles to adapt to theme:
- **Light mode**: White borders and dark shadows
- **Dark mode**: Dark borders and light shadows

#### Analytics Enhancement
- Added theme information to analytics tracking
- Map view rendered events now include current theme data

#### Map Style Updates
- Map style now automatically updates when theme changes
- Added `useEffect` to watch for theme changes and update map style accordingly

### 2. Test Updates (`src/app/(app)/__tests__/index.test.tsx`)

#### Mock Enhancements
- Updated `useColorScheme` mock to include proper interface
- Added support for testing both light and dark themes
- Enhanced Mapbox mock to include Light and Dark style URLs

#### New Test Cases
- **Light theme test**: Verifies map renders correctly in light mode
- **Dark theme test**: Verifies map renders correctly in dark mode
- **Theme change test**: Verifies smooth transition between themes
- **Analytics test**: Verifies theme information is tracked in analytics

## Available Map Styles

| Theme | Mapbox Style | URL | Description |
|-------|-------------|-----|-------------|
| Light | `Mapbox.StyleURL.Street` | `mapbox://styles/mapbox/streets-v11` | Professional street map with balanced colors |
| Dark | `Mapbox.StyleURL.Dark` | `mapbox://styles/mapbox/dark-v10` | Dark theme optimized for low light |

## Theme Detection

The component uses the `useColorScheme` hook from `nativewind` which provides:
- Current theme: `'light' | 'dark' | undefined`
- Theme setter function
- Automatic system theme detection

## User Experience

### Light Mode
- Professional street map appearance suitable for all lighting conditions
- Balanced contrast with clear, readable text and features
- Consistent with the call detail view's static map styling
- Muted colors that reduce eye strain compared to bright white themes

### Dark Mode
- Reduced brightness for low-light environments
- Dark backgrounds with light accents
- Eye-strain reduction for nighttime usage

### Automatic Switching
- Theme changes are applied immediately without restart
- Smooth transitions between light and dark styles
- Maintains map position and zoom during theme changes

## Testing Coverage

All tests pass successfully:
- ✅ Basic map rendering
- ✅ Light theme functionality
- ✅ Dark theme functionality
- ✅ Theme switching behavior
- ✅ Analytics integration with theme data

## Technical Notes

- Map style updates are handled through React state (`styleURL`)
- Theme-aware styling uses `useCallback` for performance optimization
- Analytics tracking includes theme context for usage insights
- Component maintains backward compatibility with existing functionality
- **Style Choice**: Uses `Street` style for light mode instead of `Light` style to match the professional appearance of the call detail view's static map and reduce visual brightness

## Future Enhancements

Potential improvements for future versions:
- Custom map styles for better brand integration
- Theme-specific marker colors
- Transition animations during theme changes
- User preference persistence across app restarts
128 changes: 128 additions & 0 deletions src/__tests__/security-integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/**
* Security Integration Test
*
* This test validates that the security permission checking logic works correctly
* for the calls functionality without complex component mocking.
*/

import { type DepartmentRightsResultData } from '@/models/v4/security/departmentRightsResultData';

describe('Security Permission Logic', () => {
// This mimics the logic in useSecurityStore.canUserCreateCalls
const canUserCreateCalls = (rights: DepartmentRightsResultData | null): boolean => {
return rights?.CanCreateCalls === true;
};

describe('canUserCreateCalls', () => {
it('should return true when user has CanCreateCalls permission', () => {
const rights: DepartmentRightsResultData = {
DepartmentName: 'Test Department',
DepartmentCode: 'TEST',
FullName: 'Test User',
EmailAddress: '[email protected]',
DepartmentId: '1',
IsAdmin: false,
CanViewPII: false,
CanCreateCalls: true,
CanAddNote: false,
CanCreateMessage: false,
Groups: []
};

expect(canUserCreateCalls(rights)).toBe(true);
});

it('should return false when user does not have CanCreateCalls permission', () => {
const rights: DepartmentRightsResultData = {
DepartmentName: 'Test Department',
DepartmentCode: 'TEST',
FullName: 'Test User',
EmailAddress: '[email protected]',
DepartmentId: '1',
IsAdmin: false,
CanViewPII: true,
CanCreateCalls: false,
CanAddNote: true,
CanCreateMessage: true,
Groups: []
};

expect(canUserCreateCalls(rights)).toBe(false);
});

it('should return false when rights is null', () => {
expect(canUserCreateCalls(null)).toBe(false);
});

it('should return false when CanCreateCalls is undefined', () => {
const rights = {
DepartmentName: 'Test Department',
DepartmentCode: 'TEST',
FullName: 'Test User',
EmailAddress: '[email protected]',
DepartmentId: '1',
IsAdmin: false,
CanViewPII: true,
CanAddNote: true,
CanCreateMessage: true,
Groups: []
} as unknown as DepartmentRightsResultData;

expect(canUserCreateCalls(rights)).toBe(false);
});
});

describe('UI Logic Validation', () => {
it('should show FAB when user can create calls', () => {
const rights: DepartmentRightsResultData = {
DepartmentName: 'Test Department',
DepartmentCode: 'TEST',
FullName: 'Test User',
EmailAddress: '[email protected]',
DepartmentId: '1',
IsAdmin: false,
CanViewPII: false,
CanCreateCalls: true,
CanAddNote: false,
CanCreateMessage: false,
Groups: []
};

const shouldShowFab = canUserCreateCalls(rights);
const shouldShowMenu = canUserCreateCalls(rights);

expect(shouldShowFab).toBe(true);
expect(shouldShowMenu).toBe(true);
});

it('should hide FAB and menu when user cannot create calls', () => {
const rights: DepartmentRightsResultData = {
DepartmentName: 'Test Department',
DepartmentCode: 'TEST',
FullName: 'Test User',
EmailAddress: '[email protected]',
DepartmentId: '1',
IsAdmin: false,
CanViewPII: true,
CanCreateCalls: false,
CanAddNote: true,
CanCreateMessage: true,
Groups: []
};

const shouldShowFab = canUserCreateCalls(rights);
const shouldShowMenu = canUserCreateCalls(rights);

expect(shouldShowFab).toBe(false);
expect(shouldShowMenu).toBe(false);
});

it('should hide FAB and menu when rights are not available', () => {
const shouldShowFab = canUserCreateCalls(null);
const shouldShowMenu = canUserCreateCalls(null);

expect(shouldShowFab).toBe(false);
expect(shouldShowMenu).toBe(false);
});
});
});
Loading
Loading