libra/docs/stories/story-4.3-timeline-archivin...

5.9 KiB

Story 4.3: Timeline Archiving

Epic Reference

Epic 4: Case Timeline System

User Story

As an admin, I want to archive completed cases and unarchive if needed, So that I can organize active and completed case timelines.

Story Context

Existing System Integration

  • Integrates with: timelines table (status field from Story 4.1)
  • Technology: Livewire Volt
  • Follows pattern: Soft state change pattern
  • Touch points: Timeline list views, timeline detail view

Prerequisites (from Story 4.1)

The timelines table includes:

$table->enum('status', ['active', 'archived'])->default('active');

Files to Modify

  • app/Models/Timeline.php - add archive/unarchive methods and scopes
  • resources/views/livewire/admin/timelines/show.blade.php - add archive/unarchive button
  • resources/views/livewire/admin/timelines/index.blade.php - add status filter dropdown
  • resources/views/livewire/client/timelines/index.blade.php - separate archived timelines display

Acceptance Criteria

Archive Timeline

  • Archive button on timeline detail view (admin only)
  • Confirmation modal before archiving ("Are you sure?")
  • Status changes to 'archived'
  • Timeline remains visible to client (read-only as always)
  • No further updates can be added (show disabled state with tooltip)
  • Visual indicator shows archived status (badge + muted styling)

Unarchive Timeline

  • Unarchive button on archived timelines
  • Status returns to 'active'
  • Updates can be added again

List Filtering

  • Filter dropdown with options: Active (default), Archived, All
  • Archived timelines shown in separate section in client view (below active)
  • Bulk archive via checkbox selection + "Archive Selected" button
  • Bulk archive shows count confirmation ("Archive 3 timelines?")

Edge Cases

  • Archiving already-archived timeline: No-op, no error shown
  • Unarchiving already-active timeline: No-op, no error shown
  • Adding update to archived timeline: Prevented with error message
  • Bulk archive mixed selection: Skip already-archived, archive only active ones
  • Empty bulk selection: "Archive Selected" button disabled

Quality Requirements

  • Audit log for status changes (action_type: 'archive' or 'unarchive')
  • Bilingual labels (AR/EN for buttons, messages, filters)
  • Feature tests covering all scenarios below

Test Scenarios

tests/Feature/Timeline/TimelineArchivingTest.php
- test_admin_can_archive_active_timeline
- test_admin_can_unarchive_archived_timeline
- test_archiving_archived_timeline_is_noop
- test_unarchiving_active_timeline_is_noop
- test_cannot_add_update_to_archived_timeline
- test_client_can_view_archived_timeline
- test_admin_can_filter_by_active_status
- test_admin_can_filter_by_archived_status
- test_admin_can_filter_all_statuses
- test_bulk_archive_updates_multiple_timelines
- test_bulk_archive_skips_already_archived
- test_audit_log_created_on_archive
- test_audit_log_created_on_unarchive
- test_guest_cannot_archive_timeline

Technical Notes

Timeline Model Methods

class Timeline extends Model
{
    public function archive(): void
    {
        $this->update(['status' => 'archived']);
    }

    public function unarchive(): void
    {
        $this->update(['status' => 'active']);
    }

    public function isArchived(): bool
    {
        return $this->status === 'archived';
    }

    public function scopeActive($query)
    {
        return $query->where('status', 'active');
    }

    public function scopeArchived($query)
    {
        return $query->where('status', 'archived');
    }
}

Volt Component Actions

public function archive(): void
{
    if ($this->timeline->isArchived()) {
        return;
    }

    $this->timeline->archive();

    AdminLog::create([
        'admin_id' => auth()->id(),
        'action_type' => 'archive',
        'target_type' => 'timeline',
        'target_id' => $this->timeline->id,
        'ip_address' => request()->ip(),
    ]);

    session()->flash('success', __('messages.timeline_archived'));
}

public function unarchive(): void
{
    if (!$this->timeline->isArchived()) {
        return;
    }

    $this->timeline->unarchive();

    AdminLog::create([
        'admin_id' => auth()->id(),
        'action_type' => 'unarchive',
        'target_type' => 'timeline',
        'target_id' => $this->timeline->id,
        'ip_address' => request()->ip(),
    ]);

    session()->flash('success', __('messages.timeline_unarchived'));
}

public function bulkArchive(array $ids): void
{
    Timeline::whereIn('id', $ids)->update(['status' => 'archived']);

    foreach ($ids as $id) {
        AdminLog::create([
            'admin_id' => auth()->id(),
            'action_type' => 'archive',
            'target_type' => 'timeline',
            'target_id' => $id,
            'ip_address' => request()->ip(),
        ]);
    }

    session()->flash('success', __('messages.timelines_archived', ['count' => count($ids)]));
}

Definition of Done

  • Archive button on timeline detail view works with confirmation modal
  • Unarchive button on archived timelines works
  • Update form disabled/hidden on archived timelines with clear messaging
  • Status filter dropdown functional (Active/Archived/All)
  • Bulk archive with checkbox selection works
  • Visual indicators (badge + muted styling) display correctly
  • Client can still view archived timelines in separate section
  • Audit log entries created for all archive/unarchive actions
  • All test scenarios in tests/Feature/Timeline/TimelineArchivingTest.php pass
  • Code formatted with Pint

Dependencies

  • Story 4.1: Timeline creation (docs/stories/story-4.1-timeline-creation.md) - provides Timeline model and schema
  • Story 4.2: Timeline updates (docs/stories/story-4.2-timeline-updates-management.md) - provides update blocking context

Estimation

Complexity: Low Estimated Effort: 2 hours