libra/docs/stories/story-4.4-admin-timeline-da...

5.8 KiB

Story 4.4: Admin Timeline Dashboard

Epic Reference

Epic 4: Case Timeline System

User Story

As an admin, I want a central view to manage all timelines across all clients, So that I can efficiently track and update case progress.

Story Context

Existing System Integration

  • Integrates with: timelines table, users table
  • Technology: Livewire Volt with pagination
  • Follows pattern: Admin list/dashboard pattern
  • Touch points: All timeline operations

Acceptance Criteria

List View

  • Display all timelines with:
    • Case name
    • Client name
    • Status (active/archived)
    • Last update date
    • Update count
  • Pagination (15/25/50 per page)

Filtering

  • Filter by client (search/select)
  • Filter by status (active/archived/all)
  • Filter by date range (created/updated)
  • Search by case name or reference

Sorting

  • Sort by client name
  • Sort by case name
  • Sort by last updated
  • Sort by created date

Quick Actions

  • View timeline details
  • Add update (inline or link)
  • Archive/unarchive toggle

Quality Requirements

  • Fast loading with eager loading
  • Bilingual support
  • Tests for filtering/sorting

Technical Notes

Volt Component

<?php

use App\Models\Timeline;
use Livewire\Volt\Component;
use Livewire\WithPagination;

new class extends Component {
    use WithPagination;

    public string $search = '';
    public string $clientFilter = '';
    public string $statusFilter = '';
    public string $dateFrom = '';
    public string $dateTo = '';
    public string $sortBy = 'updated_at';
    public string $sortDir = 'desc';
    public int $perPage = 15;

    public function updatedSearch()
    {
        $this->resetPage();
    }

    public function sort(string $column): void
    {
        if ($this->sortBy === $column) {
            $this->sortDir = $this->sortDir === 'asc' ? 'desc' : 'asc';
        } else {
            $this->sortBy = $column;
            $this->sortDir = 'asc';
        }
    }

    public function with(): array
    {
        return [
            'timelines' => Timeline::query()
                ->with(['user', 'updates' => fn($q) => $q->latest()->limit(1)])
                ->withCount('updates')
                ->when($this->search, fn($q) => $q->where(function($q) {
                    $q->where('case_name', 'like', "%{$this->search}%")
                      ->orWhere('case_reference', 'like', "%{$this->search}%");
                }))
                ->when($this->clientFilter, fn($q) => $q->where('user_id', $this->clientFilter))
                ->when($this->statusFilter, fn($q) => $q->where('status', $this->statusFilter))
                ->when($this->dateFrom, fn($q) => $q->where('created_at', '>=', $this->dateFrom))
                ->when($this->dateTo, fn($q) => $q->where('created_at', '<=', $this->dateTo))
                ->orderBy($this->sortBy, $this->sortDir)
                ->paginate($this->perPage),
        ];
    }
};

Template Structure

<div>
    <!-- Filters Row -->
    <div class="flex flex-wrap gap-4 mb-6">
        <flux:input wire:model.live.debounce="search" placeholder="{{ __('admin.search_cases') }}" />
        <flux:select wire:model.live="statusFilter">
            <option value="">{{ __('admin.all_statuses') }}</option>
            <option value="active">{{ __('admin.active') }}</option>
            <option value="archived">{{ __('admin.archived') }}</option>
        </flux:select>
        <!-- More filters... -->
    </div>

    <!-- Table -->
    <table class="w-full">
        <thead>
            <tr>
                <th wire:click="sort('case_name')">{{ __('admin.case_name') }}</th>
                <th wire:click="sort('user_id')">{{ __('admin.client') }}</th>
                <th>{{ __('admin.status') }}</th>
                <th wire:click="sort('updated_at')">{{ __('admin.last_update') }}</th>
                <th>{{ __('admin.actions') }}</th>
            </tr>
        </thead>
        <tbody>
            @foreach($timelines as $timeline)
                <tr>
                    <td>{{ $timeline->case_name }}</td>
                    <td>{{ $timeline->user->name }}</td>
                    <td>
                        <flux:badge :variant="$timeline->status === 'active' ? 'success' : 'secondary'">
                            {{ __('admin.' . $timeline->status) }}
                        </flux:badge>
                    </td>
                    <td>{{ $timeline->updated_at->diffForHumans() }}</td>
                    <td>
                        <flux:dropdown>
                            <flux:button size="sm">{{ __('admin.actions') }}</flux:button>
                            <flux:menu>
                                <flux:menu.item href="{{ route('admin.timelines.show', $timeline) }}">
                                    {{ __('admin.view') }}
                                </flux:menu.item>
                                <flux:menu.item wire:click="toggleArchive({{ $timeline->id }})">
                                    {{ $timeline->status === 'active' ? __('admin.archive') : __('admin.unarchive') }}
                                </flux:menu.item>
                            </flux:menu>
                        </flux:dropdown>
                    </td>
                </tr>
            @endforeach
        </tbody>
    </table>

    {{ $timelines->links() }}
</div>

Definition of Done

  • List displays all timelines
  • All filters working
  • All sorts working
  • Quick actions functional
  • Pagination working
  • No N+1 queries
  • Bilingual support
  • Tests pass
  • Code formatted with Pint

Dependencies

  • Story 4.1: Timeline creation
  • Story 4.3: Archive functionality

Estimation

Complexity: Medium Estimated Effort: 3-4 hours