generated sotries
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
# 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
|
||||
<?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
|
||||
```blade
|
||||
<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
|
||||
Reference in New Issue
Block a user