525 lines
17 KiB
PHP
525 lines
17 KiB
PHP
@props([
|
|
'pageTitle' => '',
|
|
'data' => [],
|
|
'columns' => [],
|
|
'allFields' => [],
|
|
'actions' => [],
|
|
'showAddButton' => false,
|
|
'addButtonUrl' => '#',
|
|
'showCheckboxes' => false,
|
|
'showBatchDelete' => false,
|
|
'showEditModal' => false,
|
|
'showViewModal' => false,
|
|
'baseRoute' => ''
|
|
])
|
|
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between align-items-center">
|
|
<h5 class="card-title">{{ $pageTitle }}</h5>
|
|
@if($showAddButton)
|
|
<a href="{{ $addButtonUrl }}" class="btn btn-primary">
|
|
<i class="fa-solid fa-plus me-1"></i>Add New
|
|
</a>
|
|
@endif
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover" id="dataTable">
|
|
<thead>
|
|
<tr>
|
|
@if($showCheckboxes)
|
|
<th width="50">
|
|
<input type="checkbox" class="form-check-input" id="selectAll">
|
|
</th>
|
|
@endif
|
|
@foreach($columns as $column)
|
|
<th>{{ $column['name'] }}</th>
|
|
@endforeach
|
|
@if(count($actions) > 0)
|
|
<th width="150">Actions</th>
|
|
@endif
|
|
</tr>
|
|
</thead>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@if($showEditModal)
|
|
<!-- Edit Modal -->
|
|
<div class="modal fade" id="editModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">Edit {{ $pageTitle }}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<form id="editForm">
|
|
<div class="modal-body">
|
|
@foreach($allFields as $field)
|
|
<div class="mb-3">
|
|
<label class="form-label">{{ $field['name'] }}</label>
|
|
@if($field['type'] === 'select')
|
|
<select class="form-select" name="{{ $field['key'] }}" required="{{ $field['required'] ?? false }}">
|
|
@foreach($field['options'] as $option)
|
|
<option value="{{ $option }}">{{ $option }}</option>
|
|
@endforeach
|
|
</select>
|
|
@elseif($field['type'] === 'textarea')
|
|
<textarea class="form-control" name="{{ $field['key'] }}" rows="3" required="{{ $field['required'] ?? false }}"></textarea>
|
|
@else
|
|
<input type="{{ $field['type'] }}"
|
|
class="form-control"
|
|
name="{{ $field['key'] }}"
|
|
required="{{ $field['required'] ?? false }}"
|
|
@if(isset($field['min'])) min="{{ $field['min'] }}" @endif
|
|
@if(isset($field['step'])) step="{{ $field['step'] }}" @endif>
|
|
@endif
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
|
<button type="submit" class="btn btn-primary">Save Changes</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
@if($showViewModal)
|
|
<!-- View Modal -->
|
|
<div class="modal fade" id="viewModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title">View {{ $pageTitle }}</h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
@foreach($allFields as $field)
|
|
<div class="mb-3">
|
|
<label class="fw-bold">{{ $field['name'] }}:</label>
|
|
<div id="view_{{ $field['key'] }}"></div>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
@push('scripts')
|
|
<script>
|
|
$(document).ready(function() {
|
|
// Initialize DataTable
|
|
const table = $('#dataTable').DataTable({
|
|
processing: true,
|
|
serverSide: true,
|
|
ajax: {
|
|
url: '{{ route($baseRoute . ".data") }}',
|
|
type: 'GET'
|
|
},
|
|
columns: [
|
|
@if($showCheckboxes)
|
|
{
|
|
data: null,
|
|
orderable: false,
|
|
searchable: false,
|
|
render: function() {
|
|
return '<input type="checkbox" class="form-check-input row-checkbox">';
|
|
}
|
|
},
|
|
@endif
|
|
@foreach($columns as $column)
|
|
{
|
|
data: '{{ $column["key"] }}',
|
|
name: '{{ $column["key"] }}',
|
|
render: function(data, type, row) {
|
|
if ('{{ $column["key"] }}' === 'status') {
|
|
const colors = {
|
|
'Active': 'success',
|
|
'Inactive': 'danger',
|
|
'Pending': 'warning',
|
|
'Completed': 'success',
|
|
'Cancelled': 'danger'
|
|
};
|
|
return `<span class="badge bg-${colors[data] || 'secondary'}">${data}</span>`;
|
|
}
|
|
@if(isset($column['format']) && $column['format'] === 'currency')
|
|
return new Intl.NumberFormat('en-PH', {
|
|
style: 'currency',
|
|
currency: 'PHP'
|
|
}).format(data);
|
|
@elseif(isset($column['format']) && $column['format'] === 'date')
|
|
return moment(data).format('YYYY-MM-DD HH:mm:ss');
|
|
@else
|
|
return data;
|
|
@endif
|
|
}
|
|
},
|
|
@endforeach
|
|
@if(count($actions) > 0)
|
|
{
|
|
data: null,
|
|
orderable: false,
|
|
searchable: false,
|
|
render: function(data, type, row) {
|
|
let actions = '';
|
|
@foreach($actions as $action)
|
|
@if($action === 'edit')
|
|
actions += `<button class="btn btn-sm btn-primary me-1 edit-btn" data-id="${row.id}">
|
|
<i class="fa-solid fa-pen-to-square"></i>
|
|
</button>`;
|
|
@elseif($action === 'view')
|
|
actions += `<button class="btn btn-sm btn-info me-1 view-btn" data-id="${row.id}">
|
|
<i class="fa-solid fa-eye"></i>
|
|
</button>`;
|
|
@elseif($action === 'delete')
|
|
actions += `<button class="btn btn-sm btn-danger delete-btn" data-id="${row.id}">
|
|
<i class="fa-solid fa-trash"></i>
|
|
</button>`;
|
|
@endif
|
|
@endforeach
|
|
return actions;
|
|
}
|
|
}
|
|
@endif
|
|
],
|
|
order: [[1, 'desc']]
|
|
});
|
|
|
|
// Select All Checkbox
|
|
$('#selectAll').change(function() {
|
|
$('.row-checkbox').prop('checked', $(this).prop('checked'));
|
|
updateBatchDeleteButton();
|
|
});
|
|
|
|
$(document).on('change', '.row-checkbox', function() {
|
|
updateBatchDeleteButton();
|
|
});
|
|
|
|
function updateBatchDeleteButton() {
|
|
const checkedCount = $('.row-checkbox:checked').length;
|
|
$('#batchDeleteBtn').prop('disabled', checkedCount === 0);
|
|
}
|
|
|
|
// Edit Record
|
|
$(document).on('click', '.edit-btn', function() {
|
|
const id = $(this).data('id');
|
|
$.get(`{{ route($baseRoute . ".show", ":id") }}`.replace(':id', id), function(data) {
|
|
$('#editForm').data('id', id);
|
|
@foreach($allFields as $field)
|
|
$(`#editForm [name="{{ $field['key'] }}"]`).val(data.{{ $field['key'] }});
|
|
@endforeach
|
|
$('#editModal').modal('show');
|
|
});
|
|
});
|
|
|
|
// View Record
|
|
$(document).on('click', '.view-btn', function() {
|
|
const id = $(this).data('id');
|
|
$.get(`{{ route($baseRoute . ".show", ":id") }}`.replace(':id', id), function(data) {
|
|
@foreach($allFields as $field)
|
|
@if($field['type'] === 'currency')
|
|
$('#view_{{ $field["key"] }}').text(new Intl.NumberFormat('en-PH', {
|
|
style: 'currency',
|
|
currency: 'PHP'
|
|
}).format(data.{{ $field['key'] }}));
|
|
@elseif($field['type'] === 'date')
|
|
$('#view_{{ $field["key"] }}').text(moment(data.{{ $field['key'] }}).format('YYYY-MM-DD HH:mm:ss'));
|
|
@else
|
|
$('#view_{{ $field["key"] }}').text(data.{{ $field['key'] }});
|
|
@endif
|
|
@endforeach
|
|
$('#viewModal').modal('show');
|
|
});
|
|
});
|
|
|
|
// Delete Record
|
|
$(document).on('click', '.delete-btn', function() {
|
|
const id = $(this).data('id');
|
|
if (confirm('Are you sure you want to delete this record?')) {
|
|
$.ajax({
|
|
url: `{{ route($baseRoute . ".destroy", ":id") }}`.replace(':id', id),
|
|
type: 'DELETE',
|
|
success: function() {
|
|
table.ajax.reload();
|
|
toastr.success('Record deleted successfully');
|
|
},
|
|
error: function() {
|
|
toastr.error('Failed to delete record');
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Submit Edit Form
|
|
$('#editForm').submit(function(e) {
|
|
e.preventDefault();
|
|
const id = $(this).data('id');
|
|
$.ajax({
|
|
url: `{{ route($baseRoute . ".update", ":id") }}`.replace(':id', id),
|
|
type: 'PUT',
|
|
data: $(this).serialize(),
|
|
success: function() {
|
|
$('#editModal').modal('hide');
|
|
table.ajax.reload();
|
|
toastr.success('Record updated successfully');
|
|
},
|
|
error: function(xhr) {
|
|
const errors = xhr.responseJSON.errors;
|
|
Object.keys(errors).forEach(key => {
|
|
toastr.error(errors[key][0]);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
// Batch Delete
|
|
$('#batchDeleteBtn').click(function() {
|
|
const ids = $('.row-checkbox:checked').map(function() {
|
|
return $(this).closest('tr').find('.delete-btn').data('id');
|
|
}).get();
|
|
|
|
if (ids.length && confirm('Are you sure you want to delete the selected records?')) {
|
|
$.ajax({
|
|
url: '{{ route($baseRoute . ".batch-destroy") }}',
|
|
type: 'DELETE',
|
|
data: { ids: ids },
|
|
success: function() {
|
|
table.ajax.reload();
|
|
$('#selectAll').prop('checked', false);
|
|
updateBatchDeleteButton();
|
|
toastr.success('Records deleted successfully');
|
|
},
|
|
error: function() {
|
|
toastr.error('Failed to delete records');
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
</script>
|
|
@endpush
|
|
|
|
<style>
|
|
.card,
|
|
.table,
|
|
.btn,
|
|
.form-control,
|
|
.input-group-text,
|
|
.modal-content {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
font-weight: 400;
|
|
line-height: 1.5;
|
|
}
|
|
.card-header h5,
|
|
.modal-title {
|
|
font-weight: 500;
|
|
}
|
|
.table thead th {
|
|
font-weight: 500;
|
|
font-size: 0.8rem;
|
|
}
|
|
.form-label {
|
|
font-weight: 500;
|
|
font-size: 0.9rem;
|
|
}
|
|
.card {
|
|
border-radius: 10px;
|
|
}
|
|
.card-header {
|
|
background-color: transparent;
|
|
}
|
|
.btn-primary {
|
|
background-color: #E74610;
|
|
border-color: #E74610;
|
|
}
|
|
.btn-primary:hover {
|
|
background-color: #E74610;
|
|
border-color: #E74610;
|
|
}
|
|
.sortable {
|
|
cursor: pointer;
|
|
}
|
|
.sortable:hover {
|
|
background-color: #f1f3f5;
|
|
}
|
|
.table {
|
|
font-size: 0.85rem;
|
|
width: 100%;
|
|
table-layout: auto;
|
|
}
|
|
.table th,
|
|
.table td {
|
|
padding: 0.5rem;
|
|
vertical-align: middle;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
.table th:first-child,
|
|
.table td:first-child {
|
|
width: 40px;
|
|
text-align: center;
|
|
}
|
|
.table th:last-child,
|
|
.table td:last-child {
|
|
width: 120px;
|
|
text-align: center;
|
|
}
|
|
.table td:nth-child(5),
|
|
.table th:nth-child(5) {
|
|
width: 100px;
|
|
}
|
|
.table td:nth-child(6),
|
|
.table th:nth-child(6) {
|
|
max-width: 200px;
|
|
}
|
|
.table td:nth-child(7),
|
|
.table th:nth-child(7) {
|
|
width: 120px;
|
|
}
|
|
.status-btn {
|
|
font-size: 0.75rem;
|
|
padding: 0.2rem 0.5rem;
|
|
}
|
|
.dropdown-menu-sm {
|
|
min-width: 120px;
|
|
}
|
|
.dropdown-item {
|
|
font-size: 0.85rem;
|
|
}
|
|
.table thead .sortable i {
|
|
font-size: 0.7rem;
|
|
vertical-align: middle;
|
|
}
|
|
.table-container {
|
|
overflow-x: hidden;
|
|
}
|
|
.modal-content {
|
|
border-radius: 8px;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
}
|
|
.modal-header {
|
|
border-bottom: 1px solid #e9ecef;
|
|
position: relative;
|
|
}
|
|
.modal-footer {
|
|
border-top: 1px solid #e9ecef;
|
|
}
|
|
.form-control,
|
|
.form-select {
|
|
font-size: 0.9rem;
|
|
border-radius: 5px;
|
|
}
|
|
.modal-close-btn {
|
|
background: none;
|
|
border: none;
|
|
font-size: 1rem;
|
|
color: #6c757d;
|
|
position: absolute;
|
|
right: 15px;
|
|
top: 15px;
|
|
cursor: pointer;
|
|
}
|
|
.modal-close-btn:hover {
|
|
color: #343a40;
|
|
}
|
|
.view-details p {
|
|
margin-bottom: 0.75rem;
|
|
font-size: 0.9rem;
|
|
}
|
|
.view-details strong {
|
|
display: inline-block;
|
|
width: 120px;
|
|
font-weight: 500;
|
|
color: #343a40;
|
|
}
|
|
.view-details span {
|
|
color: #495057;
|
|
}
|
|
.pagination-sm .page-link {
|
|
padding: 0.25rem 0.5rem;
|
|
font-size: 0.85rem;
|
|
border-color: #E74610;
|
|
color: #E74610;
|
|
}
|
|
.pagination-sm .page-item.active .page-link {
|
|
background-color: #E74610;
|
|
border-color: #E74610;
|
|
color: #fff;
|
|
}
|
|
.pagination-sm .page-link:hover {
|
|
background-color: #E74610;
|
|
border-color: #E74610;
|
|
color: #fff;
|
|
}
|
|
.pagination-sm .page-item.disabled .page-link {
|
|
border-color: #dee2e6;
|
|
color: #6c757d;
|
|
}
|
|
.edit-btn {
|
|
border-color: #E74610;
|
|
color: #E74610;
|
|
}
|
|
.edit-btn:hover {
|
|
background-color: #E74610;
|
|
border-color: #E74610;
|
|
color: #fff;
|
|
}
|
|
@media (max-width: 576px) {
|
|
.table {
|
|
font-size: 0.75rem;
|
|
}
|
|
.table thead th {
|
|
font-size: 0.75rem;
|
|
}
|
|
.table th,
|
|
.table td {
|
|
padding: 0.3rem;
|
|
}
|
|
.btn-sm {
|
|
padding: 0.2rem 0.4rem;
|
|
font-size: 0.75rem;
|
|
}
|
|
.input-group-sm>.form-control {
|
|
font-size: 0.75rem;
|
|
}
|
|
.status-btn {
|
|
font-size: 0.65rem;
|
|
}
|
|
.dropdown-item {
|
|
font-size: 0.75rem;
|
|
}
|
|
.table thead .sortable i {
|
|
font-size: 0.65rem;
|
|
}
|
|
.pagination-sm .page-link {
|
|
padding: 0.2rem 0.4rem;
|
|
font-size: 0.75rem;
|
|
}
|
|
.table td:nth-child(6) {
|
|
max-width: 150px;
|
|
}
|
|
.form-control,
|
|
.form-select {
|
|
font-size: 0.85rem;
|
|
}
|
|
.modal-close-btn {
|
|
font-size: 0.9rem;
|
|
}
|
|
.view-details p {
|
|
font-size: 0.85rem;
|
|
}
|
|
.view-details strong {
|
|
width: 100px;
|
|
}
|
|
}
|
|
|
|
</style> |