# Profile Image Avatar Support

## Overview
The avatar system now supports profile images! If a user has uploaded a profile picture, it will be displayed instead of their initials. This applies to all assignment displays across opportunities, projects, and other modules.

## How It Works

### 1. Database Field
Users table should have a `profile_image` field that stores the path to the uploaded image:
```sql
ALTER TABLE users ADD COLUMN profile_image VARCHAR(255) NULL;
```

### 2. Query Updates
All assignment queries need to include `u.profile_image`:

**Before:**
```php
$assignments = $db->fetchAll(
    "SELECT oa.user_id, oa.role, u.first_name, u.last_name
     FROM opportunity_assignments oa
     LEFT JOIN users u ON oa.user_id = u.id
     WHERE oa.opportunity_id = ?",
    [$id]
);
```

**After:**
```php
$assignments = $db->fetchAll(
    "SELECT oa.user_id, oa.role, u.first_name, u.last_name, u.profile_image
     FROM opportunity_assignments oa
     LEFT JOIN users u ON oa.user_id = u.id
     WHERE oa.opportunity_id = ?",
    [$id]
);
```

### 3. View Updates
In views, check for profile image before displaying initials:

**Before (initials only):**
```php
<?php
$firstInitial = strtoupper(substr($assignment['first_name'], 0, 1));
$lastInitial = strtoupper(substr($assignment['last_name'], 0, 1));
$initials = $firstInitial . $lastInitial;
$colors = ['#007bff', '#28a745', '#dc3545', '#ffc107', '#17a2b8', '#6f42c1', '#e83e8c', '#fd7e14', '#20c997', '#6610f2'];
$colorIndex = (ord($firstInitial) + ord($lastInitial)) % count($colors);
$bgColor = $colors[$colorIndex];
?>
<div class="user-avatar-circle" 
     style="background-color: <?= $bgColor ?>; color: white; width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 10px; margin-left: <?= $idx > 0 ? '-6px' : '0' ?>; border: 1px solid white; box-shadow: 0 2px 4px rgba(0,0,0,0.15); z-index: <?= 10 - $idx ?>;"
     title="<?= e($assignment['first_name'] . ' ' . $assignment['last_name']) ?>">
    <?= $initials ?>
</div>
```

**After (profile image with fallback to initials):**
```php
<?php
$profileImage = $assignment['profile_image'] ?? null;
$hasImage = !empty($profileImage) && file_exists(BASE_PATH . '/public/' . ltrim($profileImage, '/'));

if (!$hasImage) {
    // Generate initials and color
    $firstInitial = strtoupper(substr($assignment['first_name'], 0, 1));
    $lastInitial = strtoupper(substr($assignment['last_name'], 0, 1));
    $initials = $firstInitial . $lastInitial;
    $colors = ['#007bff', '#28a745', '#dc3545', '#ffc107', '#17a2b8', '#6f42c1', '#e83e8c', '#fd7e14', '#20c997', '#6610f2'];
    $colorIndex = (ord($firstInitial) + ord($lastInitial)) % count($colors);
    $bgColor = $colors[$colorIndex];
}
?>

<?php if ($hasImage): ?>
    <img src="<?= base_url($profileImage) ?>" 
         alt="<?= e($assignment['first_name'] . ' ' . $assignment['last_name']) ?>" 
         class="user-avatar-circle" 
         style="width: 24px; height: 24px; border-radius: 50%; object-fit: cover; margin-left: <?= $idx > 0 ? '-6px' : '0' ?>; border: 1px solid white; box-shadow: 0 2px 4px rgba(0,0,0,0.15); z-index: <?= 10 - $idx ?>;"
         title="<?= e($assignment['first_name'] . ' ' . $assignment['last_name']) ?>">
<?php else: ?>
    <div class="user-avatar-circle" 
         style="background-color: <?= $bgColor ?>; color: white; width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 10px; margin-left: <?= $idx > 0 ? '-6px' : '0' ?>; border: 1px solid white; box-shadow: 0 2px 4px rgba(0,0,0,0.15); z-index: <?= 10 - $idx ?>;"
         title="<?= e($assignment['first_name'] . ' ' . $assignment['last_name']) ?>">
        <?= $initials ?>
    </div>
<?php endif; ?>
```

## Using the Helper Component

A reusable component has been created at `views/components/avatar_circle.php`:

```php
<?php
// At top of file
require_once BASE_PATH . '/views/components/avatar_circle.php';
?>

<div class="avatar-stack d-flex align-items-center">
    <?php foreach (array_slice($assignments, 0, 3) as $idx => $assignment): ?>
        <?php
        renderAvatarCircle(
            $assignment,
            $size = 24,
            $overlap = $idx > 0 ? '-6px' : '0',
            $zIndex = 10 - $idx,
            $borderWidth = '1px'
        );
        ?>
    <?php endforeach; ?>
</div>
```

## Files That Need Updating

### Controllers (add `u.profile_image` to SELECT):
- [x] `controllers/CRMController.php` - Line 346
- [ ] `controllers/OpportunityController.php` - Lines 45, 340, 439
- [ ] `controllers/ProjectsController.php` - Lines 246, 307

### Views (add profile image check):
- [ ] `views/crm/opportunities.php` - Lines 191-210
- [ ] `views/crm/opportunities_pipeline.php` - Lines 142-163
- [ ] `views/crm/opportunities_show.php` - Lines 348-370
- [ ] `views/crm/opportunities_edit.php` - Lines 127-160
- [ ] `views/projects/board.php` - Lines 349-370
- [ ] `views/projects/show.php` - Lines 92-114
- [ ] `views/projects/list.php` - Lines 127-153

## Profile Image Upload

Users can upload profile images through their profile page. The image should be stored in:
```
/public/uploads/profile_images/{user_id}/
```

Example profile upload code is available in `controllers/ProfileController.php`.

## Image Requirements
- **Formats**: JPG, PNG, GIF
- **Max Size**: 2MB
- **Recommended**: Square images (200x200px or larger)
- **Storage**: `public/uploads/profile_images/`

## Testing
To test the profile image feature:
1. Upload a profile image for a user via their profile page
2. Assign that user to an opportunity or project task
3. View the opportunity list, pipeline, or project board
4. The user's profile image should appear instead of initials

If the image file is missing or not found, the system automatically falls back to displaying initials.
