# Student Learn.php Sidebar - Implementation Guide

## Overview

The sidebar on `student/learn.php` has been refactored to provide a clean, hierarchical display of course modules, topics, and lessons. This guide explains the implementation details and best practices.

---

## Architecture

### Data Flow

```
Database Query
    ↓
Flatten to Curriculum Array
    ├─ Prevent Duplicates
    ├─ Group by Module/Topic
    └─ Build Hierarchy
    ↓
Sort All Levels
    ├─ Modules by sort_order
    ├─ Topics by sort_order
    └─ Lessons by sort_order
    ↓
Render HTML
    ├─ Module Headers
    ├─ Topic Headers (Conditional)
    └─ Lesson Links
```

### PHP Implementation Steps

#### Step 1: Initialize Variables
```php
$curriculum = [];        // Main hierarchy storage
$processedLessonIds = [];  // Track processed lessons to avoid duplicates
```

#### Step 2: Build Hierarchy
```php
foreach ($lessons as $lesson) {
    // DUPLICATE CHECK
    if (in_array($lesson['id'], $processedLessonIds)) {
        continue;
    }
    $processedLessonIds[] = $lesson['id'];
    
    // NORMALIZE KEYS
    $moduleId = $lesson['module_id'] ?? 'general';
    $topicId = $lesson['topic_id'] ?? 'general';
    
    // BUILD HIERARCHY
    if (!isset($curriculum[$moduleId])) {
        $curriculum[$moduleId] = [
            'id' => $lesson['module_id'],
            'title' => $lesson['module_name'] ?? 'General',
            'sort_order' => $lesson['module_sort_order'] ?? 9999,
            'topics' => []
        ];
    }
    
    // Similar for topics and lessons...
}
```

#### Step 3: Sort Everything
```php
// Sort modules
uasort($curriculum, function($a, $b) {
    return ($a['sort_order'] ?? 9999) <=> ($b['sort_order'] ?? 9999);
});

// Sort topics within each module
foreach ($curriculum as &$module) {
    uasort($module['topics'], function($a, $b) {
        return ($a['sort_order'] ?? 9999) <=> ($b['sort_order'] ?? 9999);
    });
    
    // Sort lessons within each topic
    foreach ($module['topics'] as &$topic) {
        usort($topic['lessons'], function($a, $b) {
            return ($a['sort_order'] ?? 9999) <=> ($b['sort_order'] ?? 9999);
        });
    }
}
```

#### Step 4: Render HTML
```php
<?php foreach ($curriculum as $module): ?>
    <!-- Module Header -->
    <h3><?php echo htmlspecialchars($module['title']); ?></h3>
    
    <?php foreach ($module['topics'] as $topic): ?>
        <!-- Conditional Topic Header -->
        <?php if (count($module['topics']) > 1 || $topic['title'] !== 'General'): ?>
            <p><?php echo htmlspecialchars($topic['title']); ?></p>
        <?php endif; ?>
        
        <!-- Lesson Links -->
        <?php foreach ($topic['lessons'] as $lesson): ?>
            <a href="?id=<?php echo $courseId; ?>&lesson=<?php echo $lesson['id']; ?>">
                <!-- Icon -->
                <!-- Title -->
            </a>
        <?php endforeach; ?>
    <?php endforeach; ?>
<?php endforeach; ?>
```

---

## Key Design Decisions

### 1. Duplicate Prevention Strategy
```php
$processedLessonIds = [];

// Before processing each lesson:
if (in_array($lesson['id'], $processedLessonIds)) {
    continue;  // Skip if already processed
}
$processedLessonIds[] = $lesson['id'];
```

**Why**: Database queries might return duplicate rows if they're not perfectly optimized. This approach prevents lessons from appearing multiple times in the sidebar, regardless of query results.

### 2. Null Coalescing for Missing Levels
```php
$moduleId = $lesson['module_id'] ?? 'general';
$topicId = $lesson['topic_id'] ?? 'general';
```

**Why**: Some courses might have lessons without modules or topics. Using 'general' as a fallback key ensures these lessons are grouped logically without breaking the hierarchy.

### 3. Default Sort Order
```php
'sort_order' => $lesson['module_sort_order'] ?? 9999
```

**Why**: Items without explicit sort order are placed at the end (9999). This prevents inconsistent ordering and makes sorting predictable.

### 4. Conditional Topic Headers
```php
<?php if (count($module['topics']) > 1 || $topic['title'] !== 'General'): ?>
    <!-- Show topic header -->
<?php endif; ?>
```

**Why**: If a module has only one topic and it's named 'General', hiding the header reduces visual clutter while maintaining clarity for modules with multiple topics.

### 5. Stable Sorting Functions
```php
// uasort() - Maintains key association (for modules/topics)
uasort($curriculum, ...);

// usort() - Re-indexes array (for lessons)
usort($topic['lessons'], ...);
```

**Why**: 
- `uasort()` preserves array keys, important for ID-based lookups
- `usort()` re-indexes lessons since we only need the data, not the keys

---

## Database Dependencies

### Required Query Structure
The sidebar works with the following query result columns:

```
From course_modules:
- id (as module_id)
- title (as module_name)
- sort_order (as module_sort_order)

From course_topics:
- id (as topic_id)
- title (as topic_name)
- sort_order (as topic_sort_order)

From course_lessons:
- id (as id)
- title (as title)
- sort_order (as sort_order)

From course_lesson_progress:
- status (as progress_status)
```

### Expected Query Pattern
```php
$stmt = $db->prepare("
    SELECT cl.*,
           ct.id as topic_id, ct.title as topic_name, ct.sort_order as topic_sort_order,
           cm.id as module_id, cm.title as module_name, cm.sort_order as module_sort_order,
           clp.status as progress_status
    FROM course_modules cm
    LEFT JOIN course_topics ct ON cm.id = ct.module_id
    LEFT JOIN course_lessons cl ON ct.id = cl.topic_id
    LEFT JOIN course_lesson_progress clp ON cl.id = clp.lesson_id
    WHERE cm.course_id = ? AND ...
    ORDER BY cm.sort_order, ct.sort_order, cl.sort_order
");
```

---

## CSS Classes Used

### Tailwind CSS Classes Reference
```css
/* Spacing */
.mt-4         /* Module spacing */
.mt-2         /* Topic spacing */
.space-y-1    /* Lesson spacing */
.px-2, .px-3  /* Horizontal padding */
.py-1, .py-2.5  /* Vertical padding */
.mx-2         /* Horizontal margin */

/* Typography */
.text-xs      /* Smallest text (topics) */
.text-sm      /* Regular text (lessons) */
.font-bold, .font-semibold, .font-medium  /* Font weights */
.uppercase    /* Module headers */
.truncate     /* Lesson title overflow */

/* Colors */
.text-gray-600, .text-gray-700  /* Default text */
.text-white   /* Active lesson text */
.text-green-500  /* Completed lesson icon */
.bg-primary-dark  /* Active lesson background */
.hover:bg-gray-100  /* Hover state */

/* Interactive */
.rounded-lg   /* Rounded corners */
.transition-all  /* Smooth transitions */
.duration-200  /* Transition speed */

/* Flexbox */
.flex         /* Flex container */
.items-center  /* Vertical alignment */
.gap-3        /* Gap between items */
.flex-1       /* Flexible spacing */
.flex-shrink-0  /* Icon sizing */
```

---

## Progress Indicator Icons

### Completed Lesson (Green Checkmark)
```html
<svg xmlns="http://www.w3.org/2000/svg" 
     class="h-4 w-4 flex-shrink-0 text-green-500" 
     fill="currentColor" viewBox="0 0 20 20">
    <path fill-rule="evenodd" 
          d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" 
          clip-rule="evenodd" />
</svg>
```

### Pending Lesson (Gray Circle)
```html
<svg xmlns="http://www.w3.org/2000/svg" 
     class="h-4 w-4 flex-shrink-0 text-gray-400" 
     fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
    <path stroke-linecap="round" stroke-linejoin="round" 
          d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
```

---

## Testing Scenarios

### Scenario 1: Standard Course Structure
**Setup**: 2 Modules, each with 2 Topics, each with 3 Lessons
**Expected**: Clean hierarchy with all items visible and properly sorted

### Scenario 2: Mixed Structure
**Setup**: Some lessons in modules, some without modules
**Expected**: Unpaired items grouped under "General", all sorted correctly

### Scenario 3: Duplicate Query Results
**Setup**: Database query returns duplicate rows (edge case)
**Expected**: Each lesson appears only once in sidebar (duplicate prevention works)

### Scenario 4: Progress Tracking
**Setup**: Complete first lesson, check back
**Expected**: First lesson shows green checkmark, progress percentage updates

### Scenario 5: Custom Sort Order
**Setup**: Change lesson sort_order values in database
**Expected**: Sidebar reflects new order immediately

### Scenario 6: Long Titles
**Setup**: Module/topic/lesson with very long name (50+ chars)
**Expected**: Title truncates with ellipsis, not breaking layout

---

## Performance Considerations

### Time Complexity
```
Building Hierarchy:    O(n) where n = number of lessons
Sorting Modules:       O(m log m) where m = number of modules
Sorting Topics:        O(t log t) per module where t = topics/module
Sorting Lessons:       O(l log l) per topic where l = lessons/topic
Rendering:             O(m*t*l) where m*t*l = total items

Total: O(n + m log m + t log t + l log l + m*t*l)
```

### Memory Usage
- Minimal overhead: Only stores processed lesson IDs for duplicate checking
- Data structures are optimized for single-pass rendering
- No external libraries required

### Optimization Tips
1. **Database**: Index `(course_id, sort_order)` on modules, topics, lessons
2. **Caching**: Cache rendered sidebar if student's course doesn't change frequently
3. **Lazy Loading**: Only load sidebar on page load (not on every interaction)

---

## Error Handling

### Invalid Lesson ID
```php
// Navigation link includes lesson ID from database
// If lesson ID is invalid, lesson won't display
// No error thrown, gracefully handles missing lessons
```

### Missing Progress Status
```php
// Default to 'not_started' if progress_status is null
if ($lesson['progress_status'] === 'completed') {
    // Show checkmark
} else {
    // Show circle (covers 'not_started', null, any other status)
}
```

### Empty Course
```php
<?php if (empty($curriculum)): ?>
    <div class="text-center py-8 px-2">
        <p class="text-sm text-gray-500">No lessons available yet.</p>
    </div>
<?php endif; ?>
```

---

## Best Practices Applied

✅ **DRY Principle**: No duplicate lesson processing
✅ **KISS Principle**: Simple, straightforward logic
✅ **Security**: All output escaped with `htmlspecialchars()`
✅ **Accessibility**: Semantic HTML with proper link structure
✅ **Performance**: Single-pass data building and rendering
✅ **Maintainability**: Clear variable names and logical flow
✅ **Defensive Programming**: Null checks and fallback values
✅ **Responsive Design**: Mobile-first approach with Tailwind

---

## Future Enhancements

### Potential Improvements

1. **Collapse/Expand Modules**
   - Add toggle buttons for modules
   - Remember user's collapse preferences in localStorage

2. **Search Functionality**
   - Filter lessons by title
   - Highlight matching lessons

3. **Progress Statistics**
   - Show module completion percentage
   - Estimate time to complete course

4. **Bookmarks**
   - Allow students to bookmark favorite lessons
   - Quick access section at top

5. **Performance Metrics**
   - Show estimated vs. actual time spent
   - Progress visualization per module

6. **Mobile Experience**
   - Swipeable sidebar on mobile
   - Hamburger menu with sidebar
   - Bottom navigation bar

---

## Related Files

- `student/learn.php` - Main implementation
- `instructor/courses/course_builder.php` - Reference implementation
- Database migrations - Module, topic, lesson schema files
- `includes/functions.php` - Helper functions used

---

## Troubleshooting

### Lessons Not Appearing
1. Check database has records
2. Verify sort_order values
3. Check for duplicate query results
4. Inspect browser console for errors

### Incorrect Sorting
1. Verify sort_order values in database
2. Check for NULL values (should use default 9999)
3. Confirm sorting functions are being called

### Duplicates Appearing
1. This should never happen with new implementation
2. If it does, verify duplicate prevention code is present
3. Check database for actual duplicate rows

### Progress Not Updating
1. Verify course_lesson_progress table has records
2. Check progress_status values in database
3. Confirm page refreshes to show latest progress

---

**Last Updated**: January 13, 2026
**Version**: 2.0
**Status**: Production Ready
