339 lines
13 KiB
PHP
339 lines
13 KiB
PHP
@props([
|
|
'pageTitle' => '',
|
|
'data' => [],
|
|
'columns' => [],
|
|
'allFields' => [],
|
|
'actions' => [],
|
|
'showAddButton' => false,
|
|
'addButtonUrl' => '#',
|
|
'showCheckboxes' => false,
|
|
'showBatchDelete' => false,
|
|
'showSearch' => true,
|
|
'currentPage' => 1,
|
|
'lastPage' => 1,
|
|
'total' => 0,
|
|
])
|
|
|
|
<div class="card-body">
|
|
<!-- Filters -->
|
|
<div class="d-flex justify-content-between mb-3 flex-wrap gap-2">
|
|
<div class="d-flex align-items-center gap-2">
|
|
@if ($showSearch)
|
|
<div class="d-flex flex-column">
|
|
<label for="searchInput" class="form-label mb-1">Search</label>
|
|
<div class="input-group input-group-sm">
|
|
<span class="input-group-text bg-light border-end-0">
|
|
<i class="fa-solid fa-magnifying-glass text-muted"></i>
|
|
</span>
|
|
<input type="text" class="form-control border-start-0" placeholder="Search..." id="searchInput" data-search-param="_search" value="{{ request('_search') }}">
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
<div class="d-flex gap-2 align-items-end">
|
|
<button class="btn btn-outline-secondary btn-sm" id="clearFilters">
|
|
<i class="fa-solid fa-filter-circle-xmark me-1"></i> Clear Filters
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- No Data Message -->
|
|
@if (empty($data))
|
|
<div class="alert alert-info" id="no-data-message">No data available.</div>
|
|
@endif
|
|
|
|
<!-- Table -->
|
|
<div class="table-container">
|
|
<table class="table table-hover align-middle">
|
|
<thead class="table-light">
|
|
<tr>
|
|
@foreach ($columns as $index => $column)
|
|
<th class="{{ $column['sortable'] ? 'sortable' : '' }}" data-column="{{ $index }}" data-key="{{ $column['key'] }}">
|
|
{{ $column['name'] }}
|
|
@if ($column['sortable'])
|
|
<i class="fa-solid fa-sort"></i>
|
|
@endif
|
|
</th>
|
|
@endforeach
|
|
</tr>
|
|
</thead>
|
|
<tbody id="tableBody"></tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
<div class="d-flex justify-content-end mt-4">
|
|
<nav aria-label="Page navigation">
|
|
<ul class="pagination pagination-sm" id="pagination"></ul>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.card, .table, .btn, .form-control, .input-group-text {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
font-weight: 400;
|
|
line-height: 1.5;
|
|
}
|
|
.table-container {
|
|
overflow-x: auto;
|
|
}
|
|
.table th, .table td {
|
|
white-space: nowrap;
|
|
}
|
|
.sortable {
|
|
cursor: pointer;
|
|
position: relative;
|
|
}
|
|
.sortable:hover {
|
|
background-color: #f8f9fa;
|
|
}
|
|
.sortable i {
|
|
margin-left: 5px;
|
|
color: #6c757d;
|
|
}
|
|
.btn-export-csv {
|
|
background-color: #ff6200;
|
|
color: white;
|
|
border: none;
|
|
}
|
|
.btn-export-csv:hover {
|
|
background-color: #e65a00;
|
|
}
|
|
.input-group-text {
|
|
background-color: #f8f9fa;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
const tableConfig = {
|
|
data: @json($data),
|
|
columns: @json($columns),
|
|
pageTitle: @json($pageTitle),
|
|
csrfToken: '{{ csrf_token() }}',
|
|
currentPage: {{ $currentPage }},
|
|
lastPage: {{ $lastPage }},
|
|
total: {{ $total }},
|
|
apiEndpoint: '{{ route( Str::kebab(str_replace(" ", "-", strtolower($pageTitle)))) }}',
|
|
exportEndpoint: '{{ route( Str::kebab(str_replace(" ", "-", strtolower($pageTitle)))) }}Export',
|
|
};
|
|
|
|
console.log('Table Config:', tableConfig); // Log initial config
|
|
|
|
const rowsPerPage = 5;
|
|
let currentPage = tableConfig.currentPage;
|
|
let filteredRows = [...tableConfig.data];
|
|
let sortColumn = '';
|
|
let sortDirection = '';
|
|
|
|
function formatDateForDisplay(dateStr, isDateTime = false) {
|
|
if (!dateStr) return '';
|
|
const date = new Date(dateStr);
|
|
if (isDateTime) {
|
|
return date.toLocaleString('en-US', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit',
|
|
});
|
|
}
|
|
return date.toLocaleDateString('en-US', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: '2-digit',
|
|
}).replace(/,/, '');
|
|
}
|
|
|
|
function renderTable() {
|
|
console.log('Rendering table with filteredRows:', filteredRows); // Log data
|
|
const tableBody = document.getElementById('tableBody');
|
|
if (!tableBody) {
|
|
console.error('Table body not found');
|
|
return;
|
|
}
|
|
tableBody.innerHTML = '';
|
|
|
|
if (filteredRows.length === 0) {
|
|
tableBody.innerHTML = `<tr><td colspan="${tableConfig.columns.length}">No data to display</td></tr>`;
|
|
console.log('No data to render');
|
|
return;
|
|
}
|
|
|
|
filteredRows.forEach(row => {
|
|
const tr = document.createElement('tr');
|
|
let rowHtml = '';
|
|
tableConfig.columns.forEach(col => {
|
|
let value = row[col.key] || '';
|
|
if (col.format === 'date' || col.key.includes('DateTime')) {
|
|
value = formatDateForDisplay(value, col.key.includes('DateTime'));
|
|
}
|
|
rowHtml += `<td>${value}</td>`;
|
|
});
|
|
tr.innerHTML = rowHtml;
|
|
tableBody.appendChild(tr);
|
|
});
|
|
}
|
|
|
|
function renderPagination() {
|
|
console.log('Rendering pagination:', { currentPage, lastPage: tableConfig.lastPage }); // Log pagination
|
|
const pagination = document.getElementById('pagination');
|
|
if (!pagination) {
|
|
console.error('Pagination element not found');
|
|
return;
|
|
}
|
|
pagination.innerHTML = '';
|
|
|
|
const routeBase = tableConfig.pageTitle.toLowerCase().replace(/\s+/g, '-');
|
|
const searchTerm = document.getElementById('searchInput')?.value || '';
|
|
const startDate = document.getElementById('startDate')?.value || '';
|
|
const endDate = document.getElementById('endDate')?.value || '';
|
|
|
|
const prevLi = document.createElement('li');
|
|
prevLi.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
|
|
prevLi.innerHTML = `<a class="page-link" href="#" aria-label="Previous"><span aria-hidden="true">«</span></a>`;
|
|
prevLi.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
if (currentPage > 1) {
|
|
updateUrl(currentPage - 1, searchTerm, startDate, endDate);
|
|
}
|
|
});
|
|
pagination.appendChild(prevLi);
|
|
|
|
for (let i = 1; i <= tableConfig.lastPage; i++) {
|
|
const li = document.createElement('li');
|
|
li.className = `page-item ${currentPage === i ? 'active' : ''}`;
|
|
li.innerHTML = `<a class="page-link" href="#">${i}</a>`;
|
|
li.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
updateUrl(i, searchTerm, startDate, endDate);
|
|
});
|
|
pagination.appendChild(li);
|
|
}
|
|
|
|
const nextLi = document.createElement('li');
|
|
nextLi.className = `page-item ${currentPage === tableConfig.lastPage ? 'disabled' : ''}`;
|
|
nextLi.innerHTML = `<a class="page-link" href="#" aria-label="Next"><span aria-hidden="true">»</span></a>`;
|
|
nextLi.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
if (currentPage < tableConfig.lastPage) {
|
|
updateUrl(currentPage + 1, searchTerm, startDate, endDate);
|
|
}
|
|
});
|
|
pagination.appendChild(nextLi);
|
|
}
|
|
|
|
function updateUrl(page, search, startDate, endDate) {
|
|
console.log('Updating URL:', { page, search, startDate, endDate, sortColumn, sortDirection }); // Log URL update
|
|
const url = new URL(window.location);
|
|
url.searchParams.set('page', page);
|
|
if (search) url.searchParams.set('_search', search);
|
|
else url.searchParams.delete('_search');
|
|
if (startDate) url.searchParams.set('date_start', startDate);
|
|
else url.searchParams.delete('date_start');
|
|
if (endDate) url.searchParams.set('date_end', endDate);
|
|
else url.searchParams.delete('date_end');
|
|
if (sortColumn && sortDirection) {
|
|
url.searchParams.set('sort', `${sortColumn}|${sortDirection}`);
|
|
} else {
|
|
url.searchParams.delete('sort');
|
|
}
|
|
window.location.href = url.toString();
|
|
}
|
|
|
|
function attachEventListeners() {
|
|
const searchInput = document.getElementById('searchInput');
|
|
if (searchInput) {
|
|
searchInput.addEventListener('input', function() {
|
|
console.log('Search input changed:', this.value); // Log search
|
|
const url = new URL(window.location);
|
|
const searchTerm = this.value;
|
|
if (searchTerm) {
|
|
url.searchParams.set('_search', searchTerm);
|
|
} else {
|
|
url.searchParams.delete('_search');
|
|
}
|
|
url.searchParams.set('page', 1);
|
|
window.location.href = url.toString();
|
|
});
|
|
}
|
|
|
|
document.querySelectorAll('.sortable').forEach(header => {
|
|
header.addEventListener('click', function() {
|
|
const columnKey = this.getAttribute('data-key');
|
|
sortDirection = sortColumn === columnKey && sortDirection === 'asc' ? 'desc' : 'asc';
|
|
sortColumn = columnKey;
|
|
console.log('Sorting:', { columnKey, sortDirection }); // Log sorting
|
|
|
|
document.querySelectorAll('.sortable i').forEach(icon => {
|
|
icon.classList.remove('fa-sort-up', 'fa-sort-down');
|
|
icon.classList.add('fa-sort');
|
|
});
|
|
const icon = this.querySelector('i');
|
|
if (icon) {
|
|
icon.classList.remove('fa-sort');
|
|
icon.classList.add(sortDirection === 'asc' ? 'fa-sort-up' : 'fa-sort-down');
|
|
}
|
|
|
|
updateUrl(1, document.getElementById('searchInput')?.value || '',
|
|
document.getElementById('startDate')?.value || '',
|
|
document.getElementById('endDate')?.value || '');
|
|
});
|
|
});
|
|
|
|
const startDateInput = document.getElementById('startDate');
|
|
const endDateInput = document.getElementById('endDate');
|
|
if (startDateInput && endDateInput) {
|
|
startDateInput.addEventListener('change', () => {
|
|
console.log('Start date changed:', startDateInput.value); // Log date
|
|
updateUrl(1, document.getElementById('searchInput')?.value || '', startDateInput.value, endDateInput.value);
|
|
});
|
|
endDateInput.addEventListener('change', () => {
|
|
console.log('End date changed:', endDateInput.value); // Log date
|
|
updateUrl(1, document.getElementById('searchInput')?.value || '', startDateInput.value, endDateInput.value);
|
|
});
|
|
}
|
|
|
|
const clearFilters = document.getElementById('clearFilters');
|
|
if (clearFilters) {
|
|
clearFilters.addEventListener('click', function() {
|
|
console.log('Clearing filters'); // Log filter clear
|
|
if (searchInput) searchInput.value = '';
|
|
if (startDateInput) startDateInput.value = '';
|
|
if (endDateInput) endDateInput.value = '';
|
|
sortColumn = '';
|
|
sortDirection = '';
|
|
document.querySelectorAll('.sortable i').forEach(icon => {
|
|
icon.classList.remove('fa-sort-up', 'fa-sort-down');
|
|
icon.classList.add('fa-sort');
|
|
});
|
|
updateUrl(1, '', '', '');
|
|
});
|
|
}
|
|
|
|
const exportCsvBtn = document.getElementById('exportCsv');
|
|
if (exportCsvBtn) {
|
|
exportCsvBtn.addEventListener('click', () => {
|
|
console.log('Exporting CSV'); // Log export
|
|
const url = new URL(tableConfig.exportEndpoint, window.location.origin);
|
|
const searchTerm = document.getElementById('searchInput')?.value || '';
|
|
const startDate = document.getElementById('startDate')?.value || '';
|
|
const endDate = document.getElementById('endDate')?.value || '';
|
|
if (searchTerm) url.searchParams.set('_search', searchTerm);
|
|
if (startDate) url.searchParams.set('date_start', startDate);
|
|
if (endDate) url.searchParams.set('date_end', endDate);
|
|
if (sortColumn && sortDirection) {
|
|
url.searchParams.set('sort', `${sortColumn}|${sortDirection}`);
|
|
}
|
|
console.log('Export URL:', url.toString()); // Log export URL
|
|
window.location.href = url.toString();
|
|
});
|
|
}
|
|
}
|
|
|
|
renderTable();
|
|
renderPagination();
|
|
attachEventListeners();
|
|
</script>
|
|
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> |