feat: Implement nested product category display in admin datatable with expandable rows.
This commit is contained in:
+3
@@ -0,0 +1,3 @@
|
||||
@if($row->children->count() > 0)
|
||||
<i data-lucide="plus-circle" class="cursor-pointer text-success mx-auto" style="width:15px; height:15px"></i>
|
||||
@endif
|
||||
@@ -41,6 +41,7 @@ $tabs = [
|
||||
<div class="card-body">
|
||||
<x-datatable id="productTable" ajax="{{ route('products.group.datatable') }}"
|
||||
bulkDeleteUrl="{{ route('products.group.bulkDelete') }}" :columns="[
|
||||
['key' => 'details_control', 'label' => '', 'orderable' => false, 'searchable' => false, 'className' => 'details-control text-center cursor-pointer'],
|
||||
['key' => 'name', 'label' => 'Group'],
|
||||
['key' => 'slug', 'label' => 'Slug'],
|
||||
['key' => 'is_active', 'label' => 'Status'],
|
||||
@@ -61,17 +62,103 @@ $tabs = [
|
||||
<script src="{{ asset($datatable) }}"></script>
|
||||
@endforeach
|
||||
@endpush
|
||||
@push('custom-styles')
|
||||
<style>
|
||||
/* Lucide icon alignment */
|
||||
td.details-control {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
@push('custom-scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Re-initialize Lucide icons after DataTable draws
|
||||
$('#productTable').on('draw.dt', function() {
|
||||
setTimeout(function() {
|
||||
$('#productTable').on('draw.dt', function () {
|
||||
setTimeout(function () {
|
||||
if (typeof lucide !== 'undefined') {
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
// Disable double click on rows to prevent unwanted editing
|
||||
$('#productTable tbody').off('dblclick');
|
||||
}, 100);
|
||||
});
|
||||
|
||||
// Child Row (Details Control) Handler
|
||||
var tableId = '#productTable';
|
||||
$(tableId + ' tbody').on('click', 'td.details-control', function () {
|
||||
var tr = $(this).closest('tr');
|
||||
var table = $(tableId).DataTable();
|
||||
var row = table.row(tr);
|
||||
var cell = $(this);
|
||||
|
||||
if (row.child.isShown()) {
|
||||
// This row is already open - close it
|
||||
row.child.hide();
|
||||
tr.removeClass('shown');
|
||||
// Reset icon to Plus
|
||||
cell.html('<i data-lucide="plus-circle" class="cursor-pointer text-success mx-auto" style="width:15px; height:15px"></i>');
|
||||
} else {
|
||||
// Open this row
|
||||
if (row.data().children && row.data().children.length > 0) {
|
||||
row.child(format(row.data())).show();
|
||||
tr.addClass('shown');
|
||||
// Change icon to Minus
|
||||
cell.html('<i data-lucide="minus-circle" class="cursor-pointer text-danger mx-auto" style="width:15px; height:15px"></i>');
|
||||
|
||||
if (typeof lucide !== 'undefined') {
|
||||
lucide.createIcons();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Re-init icons for the newly added markup (plus/minus)
|
||||
if (typeof lucide !== 'undefined') {
|
||||
lucide.createIcons();
|
||||
}
|
||||
});
|
||||
|
||||
function format(d) {
|
||||
// `d` is the original data object for the row
|
||||
if (!d.children || d.children.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var childHtml = '<div class="p-3 bg-light rounded">';
|
||||
childHtml += '<h6 class="mb-2 text-primary font-weight-bold">Sub Categories</h6>';
|
||||
childHtml += '<table class="table table-sm table-bordered bg-white mb-0">';
|
||||
childHtml += '<thead class="thead-light"><tr><th>Group</th><th>Slug</th><th>Status</th><th>Action</th></tr></thead>';
|
||||
childHtml += '<tbody>';
|
||||
|
||||
d.children.forEach(function (child) {
|
||||
// Re-use logic for status badge if possible, or simple text for now
|
||||
var statusBadge = child.is_active ?
|
||||
'<span class="badge bg-success">Active</span>' :
|
||||
'<span class="badge bg-secondary">Inactive</span>';
|
||||
|
||||
// Build Action Buttons (Edit/Delete)
|
||||
var editUrl = '{{ route("products.group.edit", ":id") }}'.replace(':id', child.id);
|
||||
var actionButtons = `
|
||||
<div class="d-flex gap-2">
|
||||
<a href="${editUrl}" class="btn btn-icon btn-sm btn-outline-primary" title="Edit">
|
||||
<i data-lucide="edit-2" width="14" height="14"></i>
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
childHtml += '<tr>' +
|
||||
'<td>' + child.name + '</td>' +
|
||||
'<td>' + child.slug + '</td>' +
|
||||
'<td>' + statusBadge + '</td>' +
|
||||
'<td>' + actionButtons + '</td>' +
|
||||
'</tr>';
|
||||
});
|
||||
|
||||
childHtml += '</tbody></table></div>';
|
||||
return childHtml;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@@ -82,32 +169,32 @@ $tabs = [
|
||||
.modal-backdrop {
|
||||
z-index: 9998 !important;
|
||||
}
|
||||
|
||||
|
||||
.modal {
|
||||
z-index: 9999 !important;
|
||||
}
|
||||
|
||||
|
||||
/* Override datatable wrapper overflow to prevent clipping modals */
|
||||
.data-table-wrapper {
|
||||
overflow: visible !important;
|
||||
overflow-x: auto !important;
|
||||
}
|
||||
|
||||
|
||||
/* Reduce z-index of sticky elements to be below modal */
|
||||
.data-table-wrapper .table thead th {
|
||||
z-index: 3 !important;
|
||||
}
|
||||
|
||||
|
||||
.table thead th:last-child,
|
||||
.table tbody td:last-child {
|
||||
z-index: 2 !important;
|
||||
}
|
||||
|
||||
|
||||
.table thead th:first-child,
|
||||
.table tbody td:first-child {
|
||||
z-index: 2 !important;
|
||||
}
|
||||
|
||||
|
||||
.table thead th:nth-child(2) {
|
||||
z-index: 4 !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user