loadProjects(); } public function loadProjects() { $user = auth()->user(); $this->projects = $user->projects() ->wherePivot('role_in_project', 'client') ->with(['phases' => function ($query) { $query->select('id', 'project_id', 'name', 'progress_percent'); }]) ->get() ->toArray(); } /** * Return only project IDs the current user can access as client. */ private function accessibleProjectIds(): \Illuminate\Support\Collection { return auth()->user()->projects() ->wherePivot('role_in_project', 'client') ->pluck('projects.id'); } public function selectProject($projectId) { // Verify the project is one the user is a client on if (!$this->accessibleProjectIds()->contains((int) $projectId)) { abort(403); } $this->selectedProject = (int) $projectId; $this->loadProjectDetails(); } public function loadProjectDetails() { if (!$this->selectedProject) { return; } // Re-verify ownership on every load if (!$this->accessibleProjectIds()->contains($this->selectedProject)) { abort(403); } $project = Project::with([ 'phases', 'changeOrders', ])->find($this->selectedProject); if (!$project) { return; } $this->projectDetails = [ 'id' => $project->id, 'name' => $project->name, 'description'=> $project->description ?? '', 'start_date' => $project->start_date, 'end_date' => $project->end_date_estimated, 'status' => $project->status, 'progress' => round($project->phases->avg('progress_percent') ?? 0), ]; $mediaImages = $project->media() ->where('category', 'image') ->latest() ->take(3) ->get() ->map(fn ($media) => [ 'url' => $media->url, 'title' => $media->name, 'date' => $media->created_at->format('d/m/Y'), ]) ->toArray(); $this->galleryImages = $mediaImages ?: []; $this->changeOrders = $project->changeOrders ->sortByDesc('requested_at') ->map(fn ($order) => [ 'id' => $order->id, 'title' => $order->title, 'description' => $order->description, 'status' => $order->status, 'requested_at' => $order->requested_at?->format('d/m/Y') ?? '', 'amount' => $order->amount, ]) ->values() ->toArray(); } public function approveChangeOrder($orderId) { $changeOrder = ChangeOrder::where('id', $orderId) ->where('project_id', $this->selectedProject) ->first(); if (!$changeOrder) { abort(403); } // Verify this project is accessible by the current user if (!$this->accessibleProjectIds()->contains($this->selectedProject)) { abort(403); } $changeOrder->update([ 'status' => 'approved', 'responded_at' => now()->toDateString(), 'responded_by' => auth()->id(), ]); $this->loadProjectDetails(); $this->dispatch('changeOrderUpdated', ['id' => $orderId, 'status' => 'approved']); } public function rejectChangeOrder($orderId) { $changeOrder = ChangeOrder::where('id', $orderId) ->where('project_id', $this->selectedProject) ->first(); if (!$changeOrder) { abort(403); } // Verify this project is accessible by the current user if (!$this->accessibleProjectIds()->contains($this->selectedProject)) { abort(403); } $changeOrder->update([ 'status' => 'rejected', 'responded_at' => now()->toDateString(), 'responded_by' => auth()->id(), ]); $this->loadProjectDetails(); $this->dispatch('changeOrderUpdated', ['id' => $orderId, 'status' => 'rejected']); } public function render() { return view('livewire.client.client-projects'); } }