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;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Livewire\Component;
use Illuminate\Support\Facades\Log;
class Table extends Component
{
// Table Configuration
public $columns = []; // Columns to display
public $rows = []; // Data rows for the table
public $selected = []; // Selected row IDs
public $addRoute = null; // Route for adding new rows (optional)
public $columns = [];
public $rows = [];
public $selected = [];
public $addRoute = null;
public $hasAddButton = true;
public $hasDelete = true; // Whether the 'Add' button is shown
public $selectAll = false; // Whether 'Select All' is active
public $hasCheckbox = true; // Whether checkboxes are shown for row selection
public $hasActions = false; // Whether action buttons (like Edit/Delete) are shown
public $isViewPage = false; // Whether the table is for a view page
public $search = ''; // Search query for filtering rows
public $sortField = null; // Column to sort by
public $sortDirection = 'asc'; // Sorting direction (asc or desc)
public $hasDelete = true;
public $selectAll = false;
public $hasCheckbox = true;
public $hasActions = false;
public $isViewPage = false;
public $search = '';
public $sortField = null;
public $sortDirection = 'asc';
public bool $hasSearch = true;
public $startDate;
public $endDate;
// Pagination Configuration
public $perPage = 5;
public $page = 1;
public $renderKey = 0; // Dummy property to force re-render
// Modal Configuration
public $showModal = false; // Whether the modal is visible
public $modalMode = 'view'; // Modal mode: 'view' or 'edit'
public $modalData = []; // Data for the modal
public $showModal = false;
public $modalMode = 'view';
public $modalData = [];
/**
* Initialize the table with given columns and rows
*/
public function mount($columns, $rows, $addRoute = null)
{
$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;
Log::info("Initial rows count: " . count($this->rows));
}
/**
* View a row's details in the modal
*/
public function viewRow($id)
{
$this->modalData = collect($this->rows)->firstWhere('id', $id) ?? [];
@ -53,9 +54,6 @@ class Table extends Component
$this->showModal = true;
}
/**
* Edit a row's details in the modal
*/
public function editRow($id)
{
$this->modalData = collect($this->rows)->firstWhere('id', $id) ?? [];
@ -63,38 +61,41 @@ class Table extends Component
$this->showModal = true;
}
/**
* Close the modal
*/
public function closeModal()
{
$this->showModal = false;
$this->modalData = [];
}
/**
* Update the selected rows based on search input
*/
public function updatedSearch()
{
$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()
{
$currentRows = $this->getPaginatedRows();
if ($this->selectAll) {
$this->selected = collect($this->rows)->pluck('id')->toArray();
$this->selected = collect($currentRows)->pluck('id')->toArray();
} else {
$this->selected = [];
}
}
/**
* Select or deselect a single row
*/
public function selectRow($rowId)
{
if (in_array($rowId, $this->selected)) {
@ -104,9 +105,6 @@ class Table extends Component
}
}
/**
* Sort rows by a specific field
*/
public function sortBy($field)
{
if ($this->sortField === $field) {
@ -115,49 +113,97 @@ class Table extends Component
$this->sortField = $field;
$this->sortDirection = 'asc';
}
$this->rows = collect($this->rows)->sortBy(function ($row) {
return $row[$this->sortField] ?? null;
}, SORT_REGULAR, $this->sortDirection === 'desc')->values()->all(); // Use all() to return plain array again
$this->page = 1;
$this->forceRender();
}
/**
* Download the data from the tables
*/
public function export()
public function gotoPage($page)
{
$filename = 'export.csv';
return response()->streamDownload(function () {
// Open output stream
$handle = fopen('php://output', 'w');
// 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
fclose($handle);
}, $filename, [
'Content-Type' => 'text/csv',
'Content-Disposition' => "attachment; filename={$filename}",
]);
$totalRows = $this->getFilteredRows()->count();
$lastPage = max(1, ceil($totalRows / $this->perPage));
$newPage = min(max(1, (int) $page), $lastPage);
$this->page = $newPage;
$this->forceRender();
Log::info("Page updated to: {$this->page}, Total Rows: {$totalRows}, Last Page: {$lastPage}");
}
private function forceRender()
{
$this->renderKey++; // Increment to force Livewire to re-render
}
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()
{
$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', [
'rows' => $this->rows,
'rows' => $paginatedRows,
'totalRows' => $total,
'perPage' => $this->perPage,
'currentPage' => $this->page,
'lastPage' => $lastPage,
'renderKey' => $this->renderKey,
]);
}
}
}

View File

@ -1,48 +1,47 @@
<div class="mt-10">
<div class="p-4 bg-white shadow-md">
<div class="flex justify-between items-center mb-4">
@if ($hasSearch ?? true)
<!-- Search bar -->
<input
type="text"
wire:model="search"
placeholder="Search..."
class="border rounded px-3 py-2"
/>
@else
<!-- Start Date and End Date filters -->
<div class="flex gap-2">
<input
type="date"
wire:model="startDate"
class="border rounded px-3 py-2"
/>
<input
type="date"
wire:model="endDate"
class="border rounded px-3 py-2"
/>
<div class="flex justify-between items-center mb-4">
@if ($hasSearch ?? true)
<!-- Search bar -->
<input
type="text"
wire:model="search"
placeholder="Search..."
class="border rounded px-3 py-2"
/>
@else
<!-- Start Date and End Date filters -->
<div class="flex gap-2">
<input
type="date"
wire:model="startDate"
class="border rounded px-3 py-2"
/>
<input
type="date"
wire:model="endDate"
class="border rounded px-3 py-2"
/>
</div>
@endif
@if ($hasAddButton ?? true)
<!-- Add button -->
<a href="{{ $addRoute }}" class="bg-orange-600 text-white px-4 py-2 rounded">
+ Add
</a>
@else
<!-- Export button -->
<button
wire:click="export"
class="bg-orange-600 text-white px-4 py-2 rounded flex items-center gap-2"
>
Export CSV
</button>
@endif
</div>
@endif
@if ($hasAddButton ?? true)
<!-- Add button -->
<a href="{{ $addRoute }}" class="bg-orange-600 text-white px-4 py-2 rounded">
+ Add
</a>
@else
<!-- Export button -->
<button
wire:click="export"
class="bg-orange-600 text-white px-4 py-2 rounded flex items-center gap-2"
>
Export CSV
</button>
@endif
</div>
<!-- Table Header -->
<table class="table-auto w-full">
@ -57,16 +56,16 @@
<!-- Dynamic Columns (sortable) -->
@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'] }}
@if ($sortField === $column['field'])
@if ($sortDirection === 'asc')
<span class="ml-2">&#8593;</span>
<span class="ml-2"></span>
@else
<span class="ml-2">&#8595;</span>
@endif
<span class="ml-2"></span>
@endif
@else
<span class="ml-2">&#8597;</span>
<span class="ml-2"></span>
@endif
</th>
@endforeach
@ -80,8 +79,11 @@
<!-- Table Body -->
<tbody>
@forelse ($rows as $row)
<tr class="hover:bg-gray-50 border-b">
@php
\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) -->
@if ($hasCheckbox)
<td class="px-4 py-2">
@ -125,14 +127,56 @@
@endforelse
</tbody>
</table>
<!-- Delete Button -->
@if($hasDelete)
<button wire:click="deleteSelected"
class="mt-4 px-4 py-2 bg-red-500 text-white rounded-md hover:bg-red-600 cursor-pointer"
@if (empty($selected)) disabled @endif>
Delete
</button>
@endif
<!-- Delete Button, Pagination, and Entries in a Single Row -->
<div class="mt-4 flex items-center justify-between gap-4">
<!-- Delete Button -->
@if($hasDelete)
<button wire:click="deleteSelected"
class="px-4 py-2 bg-red-500 text-white rounded-md hover:bg-red-600 cursor-pointer"
@if (empty($selected)) disabled @endif>
Delete
</button>
@else
<div></div> <!-- Placeholder to maintain layout -->
@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 -->
@if ($showModal)
@ -143,7 +187,7 @@
<h2 class="text-lg font-semibold">
{{ $modalMode === 'view' ? 'View Details' : 'Edit Details' }}
</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>
<!-- Modal Body -->