# 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 ```php // 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 ```php // 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 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)); } }; ?>
{{-- Component template here using Flux UI --}}
``` ## 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/pages/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) - [ ] 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