pagination added [not working]

This commit is contained in:
erishBRBS 2025-04-27 09:37:55 +08:00
parent bc8f68a610
commit d49b0ff407
2 changed files with 226 additions and 136 deletions

View File

@ -2,50 +2,51 @@
namespace App\Livewire\Components; namespace App\Livewire\Components;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Livewire\Component; use Livewire\Component;
use Illuminate\Support\Facades\Log;
class Table extends Component class Table extends Component
{ {
// Table Configuration // Table Configuration
public $columns = []; // Columns to display public $columns = [];
public $rows = []; // Data rows for the table public $rows = [];
public $selected = []; // Selected row IDs public $selected = [];
public $addRoute = null; // Route for adding new rows (optional) public $addRoute = null;
public $hasAddButton = true; public $hasAddButton = true;
public $hasDelete = true; // Whether the 'Add' button is shown public $hasDelete = true;
public $selectAll = false; // Whether 'Select All' is active public $selectAll = false;
public $hasCheckbox = true; // Whether checkboxes are shown for row selection public $hasCheckbox = true;
public $hasActions = false; // Whether action buttons (like Edit/Delete) are shown public $hasActions = false;
public $isViewPage = false; // Whether the table is for a view page public $isViewPage = false;
public $search = ''; // Search query for filtering rows public $search = '';
public $sortField = null; // Column to sort by public $sortField = null;
public $sortDirection = 'asc'; // Sorting direction (asc or desc) public $sortDirection = 'asc';
public bool $hasSearch = true; public bool $hasSearch = true;
public $startDate; public $startDate;
public $endDate; public $endDate;
// Pagination Configuration
public $perPage = 5;
public $page = 1;
public $renderKey = 0; // Dummy property to force re-render
// Modal Configuration // Modal Configuration
public $showModal = false; // Whether the modal is visible public $showModal = false;
public $modalMode = 'view'; // Modal mode: 'view' or 'edit' public $modalMode = 'view';
public $modalData = []; // Data for the modal public $modalData = [];
/**
* Initialize the table with given columns and rows
*/
public function mount($columns, $rows, $addRoute = null) public function mount($columns, $rows, $addRoute = null)
{ {
$this->columns = $columns; $this->columns = $columns;
$this->rows = $rows; // Convert $rows to a plain array to ensure consistent pagination
$this->rows = collect($rows)->map(function ($row) {
return is_array($row) ? $row : (array) $row;
})->values()->toArray();
$this->addRoute = $addRoute; $this->addRoute = $addRoute;
Log::info("Initial rows count: " . count($this->rows));
} }
/**
* View a row's details in the modal
*/
public function viewRow($id) public function viewRow($id)
{ {
$this->modalData = collect($this->rows)->firstWhere('id', $id) ?? []; $this->modalData = collect($this->rows)->firstWhere('id', $id) ?? [];
@ -53,9 +54,6 @@ class Table extends Component
$this->showModal = true; $this->showModal = true;
} }
/**
* Edit a row's details in the modal
*/
public function editRow($id) public function editRow($id)
{ {
$this->modalData = collect($this->rows)->firstWhere('id', $id) ?? []; $this->modalData = collect($this->rows)->firstWhere('id', $id) ?? [];
@ -63,38 +61,41 @@ class Table extends Component
$this->showModal = true; $this->showModal = true;
} }
/**
* Close the modal
*/
public function closeModal() public function closeModal()
{ {
$this->showModal = false; $this->showModal = false;
$this->modalData = []; $this->modalData = [];
} }
/**
* Update the selected rows based on search input
*/
public function updatedSearch() public function updatedSearch()
{ {
$this->selected = []; $this->selected = [];
$this->page = 1;
$this->forceRender();
}
public function updatedStartDate()
{
$this->page = 1;
$this->forceRender();
}
public function updatedEndDate()
{
$this->page = 1;
$this->forceRender();
} }
/**
* Select or deselect all rows
*/
public function selectAllRows() public function selectAllRows()
{ {
$currentRows = $this->getPaginatedRows();
if ($this->selectAll) { if ($this->selectAll) {
$this->selected = collect($this->rows)->pluck('id')->toArray(); $this->selected = collect($currentRows)->pluck('id')->toArray();
} else { } else {
$this->selected = []; $this->selected = [];
} }
} }
/**
* Select or deselect a single row
*/
public function selectRow($rowId) public function selectRow($rowId)
{ {
if (in_array($rowId, $this->selected)) { if (in_array($rowId, $this->selected)) {
@ -104,9 +105,6 @@ class Table extends Component
} }
} }
/**
* Sort rows by a specific field
*/
public function sortBy($field) public function sortBy($field)
{ {
if ($this->sortField === $field) { if ($this->sortField === $field) {
@ -115,49 +113,97 @@ class Table extends Component
$this->sortField = $field; $this->sortField = $field;
$this->sortDirection = 'asc'; $this->sortDirection = 'asc';
} }
$this->page = 1;
$this->rows = collect($this->rows)->sortBy(function ($row) { $this->forceRender();
return $row[$this->sortField] ?? null;
}, SORT_REGULAR, $this->sortDirection === 'desc')->values()->all(); // Use all() to return plain array again
} }
/** public function gotoPage($page)
* Download the data from the tables
*/
public function export()
{ {
$filename = 'export.csv'; $totalRows = $this->getFilteredRows()->count();
$lastPage = max(1, ceil($totalRows / $this->perPage));
return response()->streamDownload(function () { $newPage = min(max(1, (int) $page), $lastPage);
// Open output stream $this->page = $newPage;
$handle = fopen('php://output', 'w'); $this->forceRender();
Log::info("Page updated to: {$this->page}, Total Rows: {$totalRows}, Last Page: {$lastPage}");
// Write the CSV header
fputcsv($handle, collect($this->columns)->pluck('label')->toArray());
// Write the rows
foreach ($this->rows as $row) {
fputcsv($handle, collect($this->columns)->map(function ($column) use ($row) {
return $row[$column['field']] ?? '';
})->toArray());
} }
// Close the output stream private function forceRender()
fclose($handle); {
}, $filename, [ $this->renderKey++; // Increment to force Livewire to re-render
'Content-Type' => 'text/csv', }
'Content-Disposition' => "attachment; filename={$filename}",
]); private function getFilteredRows()
{
$filteredRows = collect($this->rows);
// Apply search filtering
if ($this->hasSearch && $this->search) {
$filteredRows = $filteredRows->filter(function ($row) {
foreach ($this->columns as $column) {
if (stripos($row[$column['field']] ?? '', $this->search) !== false) {
return true;
}
}
return false;
});
}
// Apply date filtering
if (!$this->hasSearch && ($this->startDate || $this->endDate)) {
$filteredRows = $filteredRows->filter(function ($row) {
$date = $row['created_at'] ?? null;
if ($date) {
$date = strtotime($date);
$start = $this->startDate ? strtotime($this->startDate) : null;
$end = $this->endDate ? strtotime($this->endDate) : null;
if ($start && $date < $start) {
return false;
}
if ($end && $date > $end) {
return false;
}
}
return true;
});
}
// Apply sorting
if ($this->sortField) {
$filteredRows = $filteredRows->sortBy($this->sortField, SORT_REGULAR, $this->sortDirection === 'desc');
}
return $filteredRows->values();
}
private function getPaginatedRows()
{
$filteredRows = $this->getFilteredRows();
$start = ($this->page - 1) * $this->perPage;
$paginated = $filteredRows->slice($start, $this->perPage)->values()->toArray();
// Force Livewire to detect the change by serializing and unserializing
$result = json_decode(json_encode($paginated), true);
Log::info("Paginated rows count: " . count($result) . ", Start: {$start}, Page: {$this->page}, First Username: " . ($result[0]['username'] ?? 'N/A'));
return $result;
} }
/**
* Render the table view
*/
public function render() public function render()
{ {
$filteredRows = $this->getFilteredRows();
$total = $filteredRows->count();
$lastPage = max(1, ceil($total / $this->perPage));
$this->page = min(max(1, $this->page), $lastPage);
$paginatedRows = $this->getPaginatedRows();
Log::info("Rendering - Total Rows: {$total}, Current Page: {$this->page}, Last Page: {$lastPage}, Displayed Rows: " . count($paginatedRows));
return view('livewire.components.table', [ return view('livewire.components.table', [
'rows' => $this->rows, 'rows' => $paginatedRows,
'totalRows' => $total,
'perPage' => $this->perPage,
'currentPage' => $this->page,
'lastPage' => $lastPage,
'renderKey' => $this->renderKey,
]); ]);
} }
} }

View File

@ -41,8 +41,7 @@
Export CSV Export CSV
</button> </button>
@endif @endif
</div> </div>
<!-- Table Header --> <!-- Table Header -->
<table class="table-auto w-full"> <table class="table-auto w-full">
@ -57,16 +56,16 @@
<!-- Dynamic Columns (sortable) --> <!-- Dynamic Columns (sortable) -->
@foreach ($columns as $column) @foreach ($columns as $column)
<th class="px-4 py-2 text-left cursor-pointer " wire:click="sortBy('{{ $column['field'] }}')"> <th class="px-4 py-2 text-left cursor-pointer" wire:click="sortBy('{{ $column['field'] }}')">
{{ $column['label'] }} {{ $column['label'] }}
@if ($sortField === $column['field']) @if ($sortField === $column['field'])
@if ($sortDirection === 'asc') @if ($sortDirection === 'asc')
<span class="ml-2">&#8593;</span> <span class="ml-2"></span>
@else @else
<span class="ml-2">&#8595;</span> <span class="ml-2"></span>
@endif @endif
@else @else
<span class="ml-2">&#8597;</span> <span class="ml-2"></span>
@endif @endif
</th> </th>
@endforeach @endforeach
@ -80,8 +79,11 @@
<!-- Table Body --> <!-- Table Body -->
<tbody> <tbody>
@forelse ($rows as $row) @php
<tr class="hover:bg-gray-50 border-b"> \Illuminate\Support\Facades\Log::info("Blade rows count: " . count($rows) . ", First Username: " . ($rows[0]['username'] ?? 'N/A'));
@endphp
@forelse ($rows as $index => $row)
<tr class="hover:bg-gray-50 border-b" wire:key="row-{{ $row['id'] }}-{{ $index }}-{{ $renderKey }}">
<!-- Checkbox for row selection (if enabled) --> <!-- Checkbox for row selection (if enabled) -->
@if ($hasCheckbox) @if ($hasCheckbox)
<td class="px-4 py-2"> <td class="px-4 py-2">
@ -125,15 +127,57 @@
@endforelse @endforelse
</tbody> </tbody>
</table> </table>
<!-- Delete Button, Pagination, and Entries in a Single Row -->
<div class="mt-4 flex items-center justify-between gap-4">
<!-- Delete Button --> <!-- Delete Button -->
@if($hasDelete) @if($hasDelete)
<button wire:click="deleteSelected" <button wire:click="deleteSelected"
class="mt-4 px-4 py-2 bg-red-500 text-white rounded-md hover:bg-red-600 cursor-pointer" class="px-4 py-2 bg-red-500 text-white rounded-md hover:bg-red-600 cursor-pointer"
@if (empty($selected)) disabled @endif> @if (empty($selected)) disabled @endif>
Delete Delete
</button> </button>
@else
<div></div> <!-- Placeholder to maintain layout -->
@endif @endif
<!-- Pagination -->
<div class="flex items-center gap-2">
<!-- Previous Button -->
<button
wire:click="gotoPage({{ $currentPage - 1 }})"
class="px-3 py-1 border rounded {{ $currentPage <= 1 ? 'bg-gray-200 cursor-not-allowed' : 'bg-white hover:bg-gray-100' }}"
{{ $currentPage <= 1 ? 'disabled' : '' }}
>
Prev
</button>
<!-- Page Numbers -->
@for ($i = 1; $i <= $lastPage; $i++)
<button
wire:click="gotoPage({{ $i }})"
class="px-3 py-1 border rounded {{ $currentPage === $i ? 'bg-orange-600 text-white' : 'bg-white hover:bg-gray-100' }}"
>
{{ $i }}
</button>
@endfor
<!-- Next Button -->
<button
wire:click="gotoPage({{ $currentPage + 1 }})"
class="px-3 py-1 border rounded {{ $currentPage >= $lastPage ? 'bg-gray-200 cursor-not-allowed' : 'bg-white hover:bg-gray-100' }}"
{{ $currentPage >= $lastPage ? 'disabled' : '' }}
>
Next
</button>
</div>
<!-- Entries Text -->
<div class="text-gray-600">
Showing {{ ($currentPage - 1) * $perPage + 1 }} to {{ min($currentPage * $perPage, $totalRows) }} of {{ $totalRows }} entries
</div>
</div>
<!-- Modal for Viewing / Editing Row --> <!-- Modal for Viewing / Editing Row -->
@if ($showModal) @if ($showModal)
<div class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center z-50"> <div class="fixed inset-0 bg-gray-900 bg-opacity-50 flex items-center justify-center z-50">
@ -143,7 +187,7 @@
<h2 class="text-lg font-semibold"> <h2 class="text-lg font-semibold">
{{ $modalMode === 'view' ? 'View Details' : 'Edit Details' }} {{ $modalMode === 'view' ? 'View Details' : 'Edit Details' }}
</h2> </h2>
<button wire:click="closeModal" class="text-gray-600 hover:text-gray-800">&times;</button> <button wire:click="closeModal" class="text-gray-600 hover:text-gray-800">×</button>
</div> </div>
<!-- Modal Body --> <!-- Modal Body -->