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

16 KiB

Story 4.3: Timeline Archiving

Status

Ready for Review

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, isArchived() helper, and scopes
  • resources/views/livewire/admin/timelines/show.blade.php - add archive/unarchive buttons, confirmation modal, disable update form when archived

Note: List filtering (admin/timelines/index.blade.php) and client view separation (client/timelines/index.blade.php) are implemented in Stories 4.4 and 4.5 respectively. This story focuses on the core archive/unarchive functionality and updating the existing show page.

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 (Deferred to Story 4.4/4.5)

The following filtering features require the admin dashboard (4.4) and client view (4.5):

  • 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?")

This story provides the model methods and scopes that Stories 4.4/4.5 will use.

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 edge cases deferred to Story 4.4:

  • 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: 'archive' or 'unarchive') - uses AdminLog action field
  • Bilingual labels (AR/EN for buttons, messages)
  • Feature tests covering all scenarios below

Test Scenarios

tests/Feature/Admin/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_audit_log_created_on_archive
- test_audit_log_created_on_unarchive
- test_guest_cannot_archive_timeline
- test_client_cannot_archive_timeline
- test_archived_timeline_shows_visual_indicator
- test_update_form_disabled_on_archived_timeline

Deferred to Story 4.4/4.5:

  • 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_client_can_view_archived_timeline (4.5)

Technical Notes

Timeline Model Methods

Add these methods to app/Models/Timeline.php:

use App\Enums\TimelineStatus;

class Timeline extends Model
{
    // ... existing code ...

    public function archive(): void
    {
        $this->update(['status' => TimelineStatus::Archived]);
    }

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

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

    public function isActive(): bool
    {
        return $this->status === TimelineStatus::Active;
    }

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

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

Note: The Timeline model already casts status to TimelineStatus::class enum. Always use enum values, not strings.

Volt Component Actions

Add these methods to resources/views/livewire/admin/timelines/show.blade.php:

use App\Models\AdminLog;

public function archive(): void
{
    if ($this->timeline->isArchived()) {
        return; // No-op for already archived
    }

    $this->timeline->archive();

    AdminLog::create([
        'admin_id' => auth()->id(),
        'action' => 'archive',  // Note: field is 'action', not 'action_type'
        'target_type' => 'timeline',
        'target_id' => $this->timeline->id,
        'ip_address' => request()->ip(),
        'created_at' => now(),
    ]);

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

public function unarchive(): void
{
    if ($this->timeline->isActive()) {
        return; // No-op for already active
    }

    $this->timeline->unarchive();

    AdminLog::create([
        'admin_id' => auth()->id(),
        'action' => 'unarchive',  // Note: field is 'action', not 'action_type'
        'target_type' => 'timeline',
        'target_id' => $this->timeline->id,
        'ip_address' => request()->ip(),
        'created_at' => now(),
    ]);

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

Note: The AdminLog model uses action field (not action_type). Also requires created_at since $timestamps = false on the model.

Bulk Archive (Deferred to Story 4.4)

The bulkArchive() method will be implemented in Story 4.4 when the admin timeline index view is created.

Tasks / Subtasks

  • Task 1: Add Timeline model methods (AC: Archive/Unarchive)

    • Add archive() method using TimelineStatus::Archived enum
    • Add unarchive() method using TimelineStatus::Active enum
    • Add isArchived() helper method
    • Add isActive() helper method
    • Add scopeActive() query scope
    • Add scopeArchived() query scope
  • Task 2: Update show.blade.php component (AC: Archive/Unarchive buttons)

    • Add archive() action method with AdminLog
    • Add unarchive() action method with AdminLog
    • Add archive button (visible when timeline is active)
    • Add unarchive button (visible when timeline is archived)
    • Add confirmation modal using <flux:modal> for archive action
    • Show archived status badge using <flux:badge>
  • Task 3: Disable updates on archived timelines (AC: No updates on archived)

    • Conditionally disable/hide update form when $timeline->isArchived()
    • Show tooltip or message explaining why updates are disabled
    • Apply muted styling to archived timeline view
  • Task 4: Add translation keys (AC: Bilingual)

    • Add EN/AR keys to lang/*/messages.php
    • Add EN/AR keys to lang/*/timelines.php
  • Task 5: Write feature tests (AC: Quality Requirements)

    • Create tests/Feature/Admin/TimelineArchivingTest.php
    • Implement all test scenarios listed above
    • Verify all tests pass
  • Task 6: Code quality

    • Run vendor/bin/pint --dirty
    • Verify no regressions in existing tests

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
  • Visual indicators (badge + muted styling) display correctly
  • Audit log entries created for all archive/unarchive actions
  • All test scenarios in tests/Feature/Admin/TimelineArchivingTest.php pass
  • Code formatted with Pint

Deferred to Story 4.4/4.5:

  • Status filter dropdown functional (Admin Dashboard)
  • Bulk archive with checkbox selection (Admin Dashboard)
  • Client can still view archived timelines in separate section (Client View)

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

Dev Notes

Source Tree Reference

app/
├── Models/
│   └── Timeline.php          # Add archive/unarchive methods and scopes
├── Enums/
│   └── TimelineStatus.php    # Already exists with Active/Archived values
resources/
└── views/
    └── livewire/
        └── admin/
            └── timelines/
                └── show.blade.php  # Add archive/unarchive UI and actions
tests/
└── Feature/
    └── Admin/
        └── TimelineArchivingTest.php  # New test file

Required Translation Keys

lang/en/messages.php:

'timeline_archived' => 'Timeline has been archived.',
'timeline_unarchived' => 'Timeline has been unarchived.',
'cannot_update_archived_timeline' => 'Updates cannot be added to archived timelines.',

lang/ar/messages.php:

'timeline_archived' => 'تم أرشفة الجدول الزمني.',
'timeline_unarchived' => 'تم إلغاء أرشفة الجدول الزمني.',
'cannot_update_archived_timeline' => 'لا يمكن إضافة تحديثات إلى الجداول الزمنية المؤرشفة.',

lang/en/timelines.php:

'archive' => 'Archive',
'unarchive' => 'Unarchive',
'archive_confirm_title' => 'Archive Timeline',
'archive_confirm_message' => 'Are you sure you want to archive this timeline? No further updates can be added until it is unarchived.',
'archived_notice' => 'This timeline is archived. Updates are disabled.',

lang/ar/timelines.php:

'archive' => 'أرشفة',
'unarchive' => 'إلغاء الأرشفة',
'archive_confirm_title' => 'أرشفة الجدول الزمني',
'archive_confirm_message' => 'هل أنت متأكد من أرشفة هذا الجدول الزمني؟ لن يمكن إضافة تحديثات حتى يتم إلغاء الأرشفة.',
'archived_notice' => 'هذا الجدول الزمني مؤرشف. التحديثات معطلة.',

Flux UI Components to Use

  • <flux:button variant="danger"> - Archive button
  • <flux:button variant="primary"> - Unarchive button
  • <flux:modal> - Confirmation dialog
  • <flux:badge color="amber"> - Archived status indicator
  • <flux:tooltip> - Disabled update form explanation

Testing

Test File: tests/Feature/Admin/TimelineArchivingTest.php

Testing Pattern: Use Pest with Volt::test() for component testing (matches Stories 4.1/4.2)

Required Factories:

  • Timeline::factory() with default active status
  • Timeline::factory()->archived() state (may need to add)
  • User::factory()->admin() for admin user
  • User::factory()->client() for authorization tests

Example Test Structure:

use App\Models\Timeline;
use App\Models\User;
use App\Models\AdminLog;
use Livewire\Volt\Volt;

beforeEach(function () {
    $this->admin = User::factory()->admin()->create();
    $this->client = User::factory()->individual()->create();
    $this->timeline = Timeline::factory()->for($this->client)->create();
});

test('admin can archive active timeline', function () {
    Volt::test('admin.timelines.show', ['timeline' => $this->timeline])
        ->actingAs($this->admin)
        ->call('archive')
        ->assertHasNoErrors();

    expect($this->timeline->fresh()->isArchived())->toBeTrue();
});

Dev Agent Record

Agent Model Used

Claude Opus 4.5

File List

File Action
app/Models/Timeline.php Modified - Added archive/unarchive methods and scopes
resources/views/livewire/admin/timelines/show.blade.php Modified - Added archive/unarchive actions, buttons, modal, visual indicators
lang/en/messages.php Modified - Added archive-related translation keys
lang/ar/messages.php Modified - Added archive-related translation keys
lang/en/timelines.php Modified - Added archive UI translation keys
lang/ar/timelines.php Modified - Added archive UI translation keys
tests/Feature/Admin/TimelineArchivingTest.php Created - 17 test scenarios for archiving functionality

Debug Log References

None - Implementation completed without errors.

Completion Notes

  • All 17 archiving tests pass
  • All 312 Admin feature tests pass (no regressions)
  • Code formatted with Pint
  • Bilingual support complete (AR/EN)
  • Archive confirmation modal uses Flux UI components
  • Muted styling applied to archived timelines
  • Update form hidden with callout notice on archived timelines

Change Log

Date Version Description Author
2025-12-27 1.0 Initial story draft PM
2025-12-27 1.1 Validation fixes: re-scoped to exclude deferred features (filtering, bulk, client view), fixed AdminLog field name, fixed enum comparisons, added Tasks/Subtasks section, added translation keys, corrected test file path Claude Opus 4.5
2025-12-27 1.2 Implementation complete: Added Timeline model methods, archive/unarchive UI, translations, 17 passing tests Claude Opus 4.5

QA Results

Review Date: 2025-12-27

Reviewed By: Quinn (Test Architect)

Code Quality Assessment

Excellent implementation following Laravel/Livewire best practices. The code is clean, well-organized, and follows the established patterns in the codebase. Key strengths:

  • Model Methods: Proper use of TimelineStatus enum throughout, idempotent archive/unarchive operations
  • Volt Component: Clean separation of archive/unarchive actions with proper AdminLog audit entries
  • UI Implementation: Good UX with confirmation modal, visual indicators (badge + muted styling), and clear archived state messaging
  • Tests: Comprehensive coverage with 17 tests covering all acceptance criteria including edge cases

Refactoring Performed

None required. The implementation is clean and follows project conventions.

Compliance Check

  • Coding Standards: ✓ Pint passes with no issues
  • Project Structure: ✓ Follows existing Volt component patterns
  • Testing Strategy: ✓ Pest with Volt::test() as per codebase conventions
  • All ACs Met: ✓ All acceptance criteria verified

Improvements Checklist

All items addressed by developer:

  • Model methods (archive, unarchive, isArchived, isActive, scopes) implemented correctly
  • Archive button with confirmation modal implemented
  • Unarchive button implemented
  • Visual indicator (amber badge) for archived status
  • Muted styling (opacity-75/60) applied to archived timeline sections
  • Update form hidden with callout notice when archived
  • Edit buttons hidden on archived timeline updates
  • AdminLog audit entries created for archive/unarchive actions
  • Bilingual translations (EN/AR) complete
  • Factory state archived() available for testing
  • All 17 test scenarios pass

Security Review

No security concerns. Authorization is properly handled:

  • Admin middleware protects the route
  • Guest and client access correctly returns 401/403
  • Archive/unarchive actions only available to authenticated admins

Performance Considerations

No performance concerns. Simple status updates with minimal database operations.

Files Modified During Review

None - no modifications required.

Gate Status

Gate: PASS → docs/qa/gates/4.3-timeline-archiving.yml

✓ Ready for Done

All acceptance criteria met, comprehensive test coverage, no security or performance concerns.