# Story 6.6: Data Export - Timeline Reports ## Epic Reference **Epic 6:** Admin Dashboard ## User Story As an **admin**, I want **to export timeline and case data**, So that **I can maintain records and generate case reports**. ## Story Context ### UI Location This export feature is part of the Admin Dashboard exports section, accessible via the admin navigation. The timeline export page provides filter controls and export buttons for both CSV and PDF formats. ### Existing System Integration - **Follows pattern:** Story 6.4 (User Lists Export) and Story 6.5 (Consultation Export) - same UI layout, filter approach, and export mechanisms - **Integrates with:** Timeline model, TimelineUpdate model, User model - **Technology:** Livewire Volt, Flux UI, league/csv, barryvdh/laravel-dompdf - **Touch points:** Admin dashboard navigation, timeline management section ### Reference Documents - **Epic:** `docs/epics/epic-6-admin-dashboard.md#story-66-data-export---timeline-reports` - **Export Pattern Reference:** `docs/stories/story-6.4-data-export-user-lists.md` - establishes CSV/PDF export patterns - **Similar Implementation:** `docs/stories/story-6.5-data-export-consultation-records.md` - query and filter patterns - **Timeline System:** `docs/epics/epic-4-case-timeline.md` - timeline model and relationships - **Timeline Schema:** `docs/stories/story-4.1-timeline-creation.md#database-schema` - database structure - **PRD Export Requirements:** `docs/prd.md#117-export-functionality` - business requirements ## Acceptance Criteria ### Export Options - [ ] Export all timelines (across all clients) - [ ] Export timelines for specific client (client selector/search) ### Filters - [ ] Status filter (active/archived/all) - [ ] Date range filter (created_at) - [ ] Client filter (search by name/email) ### Export Includes - [ ] Case name and reference - [ ] Client name - [ ] Status - [ ] Created date - [ ] Number of updates - [ ] Last update date ### Formats - [ ] CSV format with bilingual headers - [ ] PDF format with Libra branding ### Optional Features - [ ] Include update content toggle (full content vs summary only) - [ ] When enabled, PDF includes all update entries per timeline ### UI Requirements - [ ] Filter controls match Story 6.4/6.5 layout - [ ] Export buttons clearly visible - [ ] Loading state during export generation - [ ] Success/error feedback messages ## Technical Notes ### File Structure ``` Routes: GET /admin/exports/timelines -> admin.exports.timelines (Volt page) Files to Create: resources/views/livewire/pages/admin/exports/timelines.blade.php (Volt component) resources/views/exports/timelines-pdf.blade.php (PDF template) Models Required (from Epic 4): app/Models/Timeline.php app/Models/TimelineUpdate.php ``` ### Database Schema Reference ```php // timelines table (from Story 4.1) // Fields: id, user_id, case_name, case_reference, status, created_at, updated_at // timeline_updates table (from Story 4.1) // Fields: id, timeline_id, admin_id, update_text, created_at, updated_at ``` ### CSV Export Implementation ```php use League\Csv\Writer; public function exportCsv(): StreamedResponse { return response()->streamDownload(function () { $csv = Writer::createFromString(); $csv->insertOne([ __('export.case_name'), __('export.case_reference'), __('export.client_name'), __('export.status'), __('export.created_date'), __('export.updates_count'), __('export.last_update'), ]); $this->getFilteredTimelines() ->cursor() ->each(fn($timeline) => $csv->insertOne([ $timeline->case_name, $timeline->case_reference ?? '-', $timeline->user->name, __('status.' . $timeline->status), $timeline->created_at->format('Y-m-d'), $timeline->updates_count, $timeline->updates_max_created_at ? Carbon::parse($timeline->updates_max_created_at)->format('Y-m-d H:i') : '-', ])); echo $csv->toString(); }, 'timelines-export-' . now()->format('Y-m-d') . '.csv'); } private function getFilteredTimelines() { return Timeline::query() ->with('user') ->withCount('updates') ->withMax('updates', 'created_at') ->when($this->clientId, fn($q) => $q->where('user_id', $this->clientId)) ->when($this->status && $this->status !== 'all', fn($q) => $q->where('status', $this->status)) ->when($this->dateFrom, fn($q) => $q->where('created_at', '>=', $this->dateFrom)) ->when($this->dateTo, fn($q) => $q->where('created_at', '<=', $this->dateTo)) ->orderBy('created_at', 'desc'); } ``` ### PDF Export Implementation ```php use Barryvdh\DomPDF\Facade\Pdf; public function exportPdf(): Response { $timelines = $this->getFilteredTimelines() ->when($this->includeUpdates, fn($q) => $q->with('updates')) ->get(); $pdf = Pdf::loadView('exports.timelines-pdf', [ 'timelines' => $timelines, 'includeUpdates' => $this->includeUpdates, 'generatedAt' => now(), 'filters' => [ 'status' => $this->status, 'dateFrom' => $this->dateFrom, 'dateTo' => $this->dateTo, 'client' => $this->clientId ? User::find($this->clientId)?->name : null, ], ]); return $pdf->download('timelines-report-' . now()->format('Y-m-d') . '.pdf'); } ``` ### Volt Component Structure ```php clientSearch) < 2) { return collect(); } return User::query() ->whereIn('user_type', ['individual', 'company']) ->where(fn($q) => $q ->where('name', 'like', "%{$this->clientSearch}%") ->orWhere('email', 'like', "%{$this->clientSearch}%")) ->limit(10) ->get(); } public function selectClient(int $id): void { $this->clientId = $id; $this->clientSearch = User::find($id)?->name ?? ''; } public function clearClient(): void { $this->clientId = null; $this->clientSearch = ''; } public function exportCsv(): StreamedResponse { /* see above */ } public function exportPdf(): Response { /* see above */ } }; ?>
{{ __('export.generated_at') }}: {{ $generatedAt->format('Y-m-d H:i') }}
| {{ __('export.case_name') }} | {{ __('export.case_reference') }} | {{ __('export.client_name') }} | {{ __('export.status') }} | {{ __('export.created_date') }} | {{ __('export.updates_count') }} | {{ __('export.last_update') }} |
|---|---|---|---|---|---|---|
| {{ $timeline->case_name }} | {{ $timeline->case_reference ?? '-' }} | {{ $timeline->user->name }} | {{ __('status.' . $timeline->status) }} | {{ $timeline->created_at->format('Y-m-d') }} | {{ $timeline->updates_count }} | {{ $timeline->updates_max_created_at ? Carbon::parse($timeline->updates_max_created_at)->format('Y-m-d H:i') : '-' }} |
|
{{ __('export.updates') }}:
@foreach($timeline->updates as $update)
{{ $update->created_at->format('Y-m-d H:i') }}
@endforeach
{{ Str::limit($update->update_text, 500) }} |
||||||
| {{ __('export.no_records') }} | ||||||