Skip to main content

Overview

The HealthHistory component provides a comprehensive, paginated list of all health records (appointments, exercises, weight, and heart rate measurements). It supports search, filtering by record type, and deletion with automatic attachment cleanup. Namespace: App\Livewire\History\HealthHistory
Route: GET /history

Properties

Search query for filtering appointments and exercises by title/description
type
string
Filter by record type: '' (all), 'appointment', 'exercise', 'weight', or 'heart'
perPage
int
default:"20"
Number of records to display per page

Traits

  • WithPagination - Enables pagination of results
  • HasContext - Provides $this->getTargetUserId() and $this->isReadOnly for viewer mode

Event Listeners

refresh-history
event
Refreshes the component after a record is added, edited, or deleted
app/Livewire/History/HealthHistory.php:26
protected $listeners = ['refresh-history' => '$refresh'];

Methods

deleteRecord()

Deletes a health record and its associated attachments.
app/Livewire/History/HealthHistory.php:32-60
public function deleteRecord($id, $type)
{
    if($this->isReadOnly) {
        return;
    }

    $modelClass = match($type) {
        'MedicalAppointment' => MedicalAppointment::class,
        'ActivityExercise' => ActivityExercise::class,
        'MeasurementWeight' => MeasurementWeight::class,
        'MeasurementHeart' => MeasurementHeart::class,
        default => null,
    };
    if (!$modelClass) return;

    $record = $modelClass::where('id', $id)->where('user_id', auth()->id())->first();

    if ($record) {
        if (method_exists($record, 'attachments')) {
            foreach ($record->attachments as $attachment) {
                if (Storage::disk('local')->exists($attachment->file_path)) {
                    Storage::disk('local')->delete($attachment->file_path);
                }
                $attachment->delete();
            }
        }
        $record->delete();
    }
}
id
int
required
The ID of the record to delete
type
string
required
One of: MedicalAppointment, ActivityExercise, MeasurementWeight, MeasurementHeart
Read-Only Protection: Deletes are blocked if $this->isReadOnly is true (viewer mode). Cascade Delete: For appointments and exercises, all attachments are deleted from disk and database before deleting the record.

render()

Builds and paginates the combined record list.
app/Livewire/History/HealthHistory.php:62-112
public function render()
{
    $userId = $this->getTargetUserId();
    $allRecords = collect();

    // Load appointments
    if (empty($this->type) || $this->type === 'appointment') {
        $query = MedicalAppointment::where('user_id', $userId);
        if (!empty($this->search)) {
            $query->where(function (Builder $q) {
                $q->where('title', 'like', '%' . $this->search . '%')
                  ->orWhere('description', 'like', '%' . $this->search . '%');
            });
        }
        $allRecords = $allRecords->concat($query->latest('date')->get());
    }

    // Load exercises
    if (empty($this->type) || $this->type === 'exercise') {
        $query = ActivityExercise::where('user_id', $userId);
        if (!empty($this->search)) {
            $query->where(function (Builder $q) {
                $q->where('title', 'like', '%' . $this->search . '%')
                  ->orWhere('description', 'like', '%' . $this->search . '%');
            });
        }
        $allRecords = $allRecords->concat($query->latest('date')->get());
    }

    // Load weight (no search)
    if (empty($this->search) && (empty($this->type) || $this->type === 'weight')) {
        $allRecords = $allRecords->concat(MeasurementWeight::where('user_id', $userId)->latest('date')->get());
    }

    // Load heart rate (no search)
    if (empty($this->search) && (empty($this->type) || $this->type === 'heart')) {
        $allRecords = $allRecords->concat(MeasurementHeart::where('user_id', $userId)->latest('date')->get());
    }

    $sortedRecords = $allRecords->sortByDesc('date');

    // Manual pagination
    $items = $sortedRecords->forPage($this->getPage(), $this->perPage);

    $paginatedRecords = new LengthAwarePaginator(
        $items,
        $sortedRecords->count(),
        $this->perPage,
        $this->getPage(),
        ['path' => request()->url(), 'query' => request()->query()]
    );

    return view('livewire.history.health-history', [
        'records' => $paginatedRecords
    ])->layout('layouts.app');
}
Search Behavior:
  • Only appointments and exercises are searchable (title and description fields)
  • Weight and heart measurements are excluded when a search query is active
Filtering:
  • Empty type shows all records
  • Specific type shows only that record type
Pagination:
  • Records from all types are combined into a single collection
  • Sorted by date (newest first)
  • Manually paginated using Laravel’s LengthAwarePaginator

UI Features

Search Bar:
<input wire:model.live="search" type="text" placeholder="Search appointments and exercises...">
Type Filter:
<select wire:model.live="type">
    <option value="">All Types</option>
    <option value="appointment">Appointments</option>
    <option value="exercise">Exercise</option>
    <option value="weight">Weight</option>
    <option value="heart">Heart Rate</option>
</select>
Per-Page Selector:
<select wire:model.live="perPage">
    <option value="10">10</option>
    <option value="20">20</option>
    <option value="50">50</option>
    <option value="100">100</option>
</select>

Property Watchers

All three properties reset pagination when changed:
app/Livewire/History/HealthHistory.php:28-30
public function updatedSearch() { $this->resetPage(); }
public function updatedType() { $this->resetPage(); }
public function updatedPerPage() { $this->resetPage(); }

Viewer Mode Support

When viewing another user’s data:
  • $this->getTargetUserId() returns the selected user ID from session
  • $this->isReadOnly is true, preventing deletions
  • Delete buttons are hidden in the UI

Dependencies

  • All health models - MedicalAppointment, ActivityExercise, MeasurementWeight, MeasurementHeart
  • Attachment Model - For cascade deletion
  • Storage Facade - For deleting attachment files
  • HasContext Trait - For viewer mode support

HealthStats Component

View weight trends and statistics

Export Controller

Export all records to PDF

Build docs developers (and LLMs) love