libra/docs/stories/story-4.1-timeline-creation.md

15 KiB

Story 4.1: Timeline Creation

Epic Reference

Epic 4: Case Timeline System

User Story

As an admin, I want to create case timelines for clients, So that I can track and communicate progress on their legal matters.

Story Context

Existing System Integration

  • Integrates with: timelines table, timeline_updates table, users table, admin_logs table
  • Technology: Livewire Volt, Flux UI
  • Follows pattern: Admin CRUD pattern (see existing admin components)
  • Touch points: User relationship, client dashboard, audit logging

Reference Documents

  • Epic: docs/epics/epic-4-case-timeline.md
  • Database Schema: docs/stories/story-1.1-project-setup-database-schema.md#database-schema-reference
  • User Model: Requires users with user_type of 'individual' or 'company' (from Epic 2)

Acceptance Criteria

Timeline Creation Form

  • Select client (search by name/email) - only users with user_type 'individual' or 'company'
  • Case name/title (required)
  • Case reference number (optional, unique if provided)
  • Initial notes (optional)

Behavior

  • Timeline assigned to selected client
  • Creation date automatically recorded
  • Status defaults to 'active'
  • Can create multiple timelines per client
  • Confirmation message on successful creation
  • Timeline immediately visible to client

Validation

  • Case name required
  • Case reference unique if provided
  • Client must exist

Quality Requirements

  • Audit log entry created via AdminLog model
  • Bilingual labels and messages
  • Tests for creation flow

Technical Notes

File Structure

Routes:
  GET  /admin/timelines/create -> admin.timelines.create
  POST handled by Livewire component

Files to Create:
  resources/views/livewire/pages/admin/timelines/create.blade.php (Volt component)

Models Required (from Story 1.1):
  app/Models/Timeline.php
  app/Models/TimelineUpdate.php
  app/Models/AdminLog.php

Database Schema

// timelines table (from Story 1.1)
Schema::create('timelines', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained()->cascadeOnDelete();
    $table->string('case_name');
    $table->string('case_reference')->nullable()->unique();
    $table->enum('status', ['active', 'archived'])->default('active');
    $table->timestamps();
});

// timeline_updates table (from Story 1.1)
Schema::create('timeline_updates', function (Blueprint $table) {
    $table->id();
    $table->foreignId('timeline_id')->constrained()->cascadeOnDelete();
    $table->foreignId('admin_id')->constrained('users')->cascadeOnDelete();
    $table->text('update_text');
    $table->timestamps();
});

Required Translation Keys

// resources/lang/en/messages.php
'timeline_created' => 'Timeline created successfully.',

// resources/lang/ar/messages.php
'timeline_created' => 'تم إنشاء الجدول الزمني بنجاح.',

// resources/lang/en/labels.php
'case_name' => 'Case Name',
'case_reference' => 'Case Reference',
'initial_notes' => 'Initial Notes',
'select_client' => 'Select Client',
'search_client' => 'Search by name or email...',
'create_timeline' => 'Create Timeline',

// resources/lang/ar/labels.php
'case_name' => 'اسم القضية',
'case_reference' => 'رقم مرجع القضية',
'initial_notes' => 'ملاحظات أولية',
'select_client' => 'اختر العميل',
'search_client' => 'البحث بالاسم أو البريد الإلكتروني...',
'create_timeline' => 'إنشاء جدول زمني',

Volt Component

<?php

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

new class extends Component {
    public string $search = '';
    public ?int $selectedUserId = null;
    public ?User $selectedUser = null;
    public string $caseName = '';
    public string $caseReference = '';
    public string $initialNotes = '';

    public function updatedSearch(): void
    {
        // Reset selection when search changes
        if ($this->selectedUser && ! str_contains(strtolower($this->selectedUser->name), strtolower($this->search))) {
            $this->selectedUserId = null;
            $this->selectedUser = null;
        }
    }

    public function getClientsProperty()
    {
        if (strlen($this->search) < 2) {
            return collect();
        }

        return User::query()
            ->whereIn('user_type', ['individual', 'company'])
            ->where('status', 'active')
            ->where(function ($query) {
                $query->where('name', 'like', "%{$this->search}%")
                    ->orWhere('email', 'like', "%{$this->search}%");
            })
            ->limit(10)
            ->get();
    }

    public function selectUser(int $userId): void
    {
        $this->selectedUserId = $userId;
        $this->selectedUser = User::find($userId);
        $this->search = $this->selectedUser->name;
    }

    public function create(): void
    {
        $this->validate([
            'selectedUserId' => ['required', 'exists:users,id'],
            'caseName' => ['required', 'string', 'max:255'],
            'caseReference' => ['nullable', 'string', 'max:50', 'unique:timelines,case_reference'],
        ]);

        $timeline = Timeline::create([
            'user_id' => $this->selectedUserId,
            'case_name' => $this->caseName,
            'case_reference' => $this->caseReference ?: null,
            'status' => 'active',
        ]);

        // Add initial notes as first update if provided
        if ($this->initialNotes) {
            $timeline->updates()->create([
                'admin_id' => auth()->id(),
                'update_text' => $this->initialNotes,
            ]);
        }

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

        session()->flash('success', __('messages.timeline_created'));
        $this->redirect(route('admin.timelines.show', $timeline));
    }
}; ?>

<div>
    {{-- Component template here using Flux UI --}}
</div>

Test Scenarios

All tests should use Pest and be placed in tests/Feature/Admin/TimelineCreationTest.php.

Happy Path Tests

  • test_admin_can_view_timeline_creation_form - Admin can access /admin/timelines/create
  • test_admin_can_search_clients_by_name - Search returns matching users
  • test_admin_can_search_clients_by_email - Search returns matching users
  • test_admin_can_create_timeline_with_required_fields - Timeline created with case_name only
  • test_admin_can_create_timeline_with_case_reference - Timeline created with optional reference
  • test_initial_notes_creates_first_timeline_update - TimelineUpdate record created
  • test_audit_log_created_on_timeline_creation - AdminLog entry exists

Validation Tests

  • test_case_name_is_required - Validation error without case_name
  • test_case_reference_must_be_unique - Validation error on duplicate reference
  • test_case_reference_allows_multiple_nulls - Multiple timelines without reference allowed
  • test_client_selection_is_required - Validation error without selecting client
  • test_selected_client_must_exist - Validation error for non-existent user_id

Authorization Tests

  • test_non_admin_cannot_access_timeline_creation - Redirect or 403 for non-admin users
  • test_guest_cannot_access_timeline_creation - Redirect to login

Edge Case Tests

  • test_search_only_returns_individual_and_company_users - Admin users not in results
  • test_search_only_returns_active_users - Deactivated users not in results
  • test_can_create_multiple_timelines_for_same_client - No unique constraint on user_id

Definition of Done

  • Volt component created at resources/views/livewire/admin/timelines/create.blade.php
  • Route registered for admin timeline creation
  • Can search and select client (individual/company only)
  • Can enter case name and reference
  • Timeline created with correct data
  • Initial notes saved as first update
  • Unique reference validation works
  • Client can view timeline immediately (verified via client dashboard) - Requires future story for client timeline view
  • Audit log created
  • All translation keys added (AR/EN)
  • All tests pass
  • Code formatted with Pint

Dependencies

  • Story 1.1: Database schema (timelines, timeline_updates, admin_logs tables and models)
  • Story 1.2: Authentication & role system (admin role check)
  • Story 2.1/2.2: User accounts exist to assign timelines to

Estimation

Complexity: Low-Medium


QA Results

Review Date: 2025-12-27

Reviewed By: Quinn (Test Architect)

Code Quality Assessment

Overall: Excellent implementation. The timeline creation feature is well-structured, follows established codebase patterns, and demonstrates strong adherence to Laravel/Livewire best practices. The code is clean, maintainable, and properly tested.

Strengths:

  • Class-based Volt component pattern matches existing admin components exactly
  • Proper use of User model scopes (clients(), active()) instead of raw queries
  • Correct implementation of AdminLog with existing field names (action vs story spec's action_type)
  • Flux UI components used consistently throughout
  • Bilingual support complete with both EN/AR translation files
  • Comprehensive test coverage (22 tests) covering happy paths, validation, authorization, and edge cases

Refactoring Performed

None required. The implementation is clean and follows established patterns.

Compliance Check

  • Coding Standards: ✓ Follows class-based Volt pattern, uses Flux UI, proper validation
  • Project Structure: ✓ Files placed in correct locations matching existing structure
  • Testing Strategy: ✓ Pest tests with Volt::test() pattern, comprehensive coverage
  • All ACs Met: ✓ All acceptance criteria fulfilled (see traceability below)

Requirements Traceability (Given-When-Then)

AC# Acceptance Criteria Test Coverage Status
1 Select client (search by name/email) - only individual/company users test_admin_can_search_clients_by_name, test_admin_can_search_clients_by_email, test_search_only_returns_individual_and_company_users
2 Case name/title (required) test_case_name_is_required, test_admin_can_create_timeline_with_required_fields
3 Case reference (optional, unique if provided) test_admin_can_create_timeline_with_case_reference, test_case_reference_must_be_unique, test_case_reference_allows_multiple_nulls
4 Initial notes (optional) test_initial_notes_creates_first_timeline_update, test_timeline_without_initial_notes_has_no_updates
5 Timeline assigned to selected client test_admin_can_create_timeline_with_required_fields (verifies user_id)
6 Creation date automatically recorded Timestamps handled by Eloquent
7 Status defaults to 'active' test_timeline_status_defaults_to_active
8 Can create multiple timelines per client test_can_create_multiple_timelines_for_same_client
9 Confirmation message on success Component uses session()->flash('success', ...)
10 Client must exist validation test_selected_client_must_exist
11 Audit log entry created test_audit_log_created_on_timeline_creation
12 Bilingual labels/messages EN/AR translation files complete
13 Authorization tests test_non_admin_cannot_access_timeline_creation, test_guest_cannot_access_timeline_creation
14 Search only returns active users test_search_only_returns_active_users

Improvements Checklist

All items completed by the developer:

  • Volt component created with proper class-based pattern
  • Client search implemented with debounce for performance
  • Validation rules properly defined
  • AdminLog integration working correctly
  • Translation files complete for both languages
  • All 22 tests passing
  • Code formatted with Pint

Security Review

Status: PASS

  • Authorization: Admin middleware properly protects the route (routes/web.php:83-85)
  • Validation: All user inputs validated before database operations
  • SQL Injection: Uses Eloquent query builder with parameterized queries
  • CSRF: Handled by Livewire automatically
  • Mass Assignment: Only fillable attributes used

Performance Considerations

Status: PASS

  • Client search uses limit(10) to prevent excessive results
  • Debounce (300ms) on search input reduces server requests
  • Minimum 2-character search threshold prevents over-querying
  • Eager loading not needed (no relationship hydration in search)

Files Modified During Review

None. No refactoring was necessary.

Gate Status

Gate: PASS → docs/qa/gates/4.1-timeline-creation.yml

Ready for Done

All acceptance criteria have been met, tests pass, code follows established patterns, and no security or performance concerns were identified.


Dev Agent Record

Status

Ready for Review

Agent Model Used

Claude Opus 4.5 (claude-opus-4-5-20251101)

File List

Created:

  • resources/views/livewire/admin/timelines/create.blade.php - Volt component for timeline creation
  • lang/en/timelines.php - English translation keys for timelines
  • lang/ar/timelines.php - Arabic translation keys for timelines
  • tests/Feature/Admin/TimelineCreationTest.php - 22 Pest tests for timeline creation

Modified:

  • routes/web.php - Added admin.timelines.create route
  • lang/en/messages.php - Added timeline_created message
  • lang/ar/messages.php - Added timeline_created message (Arabic)

Change Log

  1. Created Volt component with client search, case name/reference inputs, initial notes
  2. Implemented searchable client dropdown (individual/company users only, active only)
  3. Added validation rules (case name required, case reference unique)
  4. Created audit logging on timeline creation
  5. Initial notes saved as first TimelineUpdate if provided
  6. Added bilingual translation support (EN/AR)
  7. Registered route at /admin/timelines/create
  8. Created 22 comprehensive Pest tests covering all scenarios

Completion Notes

  • Component path adjusted from pages/admin/timelines/ to admin/timelines/ to match existing codebase structure
  • Redirect after creation goes to admin.dashboard (not admin.timelines.show) since show route is in a future story
  • Used existing User model scopes (clients(), active()) instead of raw queries
  • AdminLog uses action field (not action_type as in story spec) to match existing model
  • One DoD item remains unchecked: "Client can view timeline immediately" requires client dashboard timeline view (future story)