Files
CKB/resources/views/reports/stock-products.blade.php

576 lines
17 KiB
PHP

@extends('layouts.backapp')
@section('styles')
<style>
/* Loading overlay */
#loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
backdrop-filter: blur(2px);
}
#loading-overlay .spinner-border {
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Button loading state */
.btn.loading {
opacity: 0.7;
cursor: not-allowed;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* Table styling */
.table th {
background-color: #f8f9fa;
border-color: #dee2e6;
font-weight: 600;
color: #495057;
}
.table td {
vertical-align: middle;
}
/* Dealer column styling */
.dealer-column {
background-color: #f8f9fa;
min-width: 120px;
text-align: center;
}
/* Filter section styling */
.form-label.font-weight-bold {
color: #495057;
margin-bottom: 0.5rem;
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.input-group-text {
background-color: #ffffff;
border-color: #ced4da;
color: #6c757d;
border-right: 0;
font-size: 0.875rem;
}
.input-group .form-control {
border-left: 0;
border-right: 1px solid #ced4da;
}
.input-group .form-control:focus {
border-color: #80bdff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
z-index: 3;
}
.input-group .form-control:focus + .input-group-append .input-group-text {
border-color: #80bdff;
}
.input-group .form-control:focus ~ .input-group-prepend .input-group-text {
border-color: #80bdff;
z-index: 4;
}
.input-group:hover .form-control {
border-color: #adb5bd;
}
.input-group:hover .input-group-text {
border-color: #adb5bd;
}
/* Datepicker styling */
.datepicker {
border-radius: 0.375rem;
border: 1px solid #ced4da;
transition: all 0.15s ease-in-out;
font-size: 0.875rem;
}
.datepicker:focus {
border-color: #80bdff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
.datepicker::placeholder {
color: #6c757d;
opacity: 0.7;
}
.datepicker[readonly] {
background-color: #ffffff;
cursor: pointer;
}
.datepicker[readonly]:hover {
background-color: #f8f9fa;
}
/* Button styling */
.btn-primary {
background-color: #007bff;
border-color: #007bff;
font-weight: 500;
transition: all 0.15s ease-in-out;
height: 38px;
display: flex;
align-items: center;
justify-content: center;
}
.btn-primary:hover {
background-color: #0056b3;
border-color: #0056b3;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.btn-primary:focus {
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
.btn-primary:active {
transform: translateY(0);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
.btn i {
margin-right: 0.5rem;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.row.mb-4 {
padding: 1rem;
}
.col-md-2 {
margin-top: 1rem;
}
.btn-primary {
height: 42px;
font-size: 0.9rem;
}
.form-label.font-weight-bold {
font-size: 0.8rem;
}
}
@media (max-width: 576px) {
.row.mb-4 {
padding: 0.75rem;
}
.input-group {
flex-direction: column;
}
.input-group-prepend {
margin-bottom: 0.5rem;
}
.input-group .form-control {
border-left: 1px solid #ced4da;
border-radius: 0.375rem;
}
}
</style>
@endsection
@section('content')
<div class="kt-content" id="kt_content">
<div class="kt-portlet kt-portlet--mobile" id="kt_blockui_datatable">
<div class="kt-portlet__head kt-portlet__head--lg">
<div class="kt-portlet__head-label">
<h3 class="kt-portlet__head-title">
Laporan Stok Produk
</h3>
</div>
<div class="kt-portlet__head-toolbar">
<div class="kt-portlet__head-actions">
<button type="button" class="btn btn-bold btn-success btn--sm" id="btn-export-excel">
Export
</button>
</div>
</div>
</div>
<div class="kt-portlet__body">
<!-- Filter Section -->
<div class="row mb-4">
<div class="col-md-4">
<div class="form-group mb-0">
<label class="form-label font-weight-bold">Filter Tanggal</label>
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa fa-calendar"></i>
</span>
</div>
<input type="text" class="form-control datepicker" id="filter-date" placeholder="Pilih tanggal" value="{{ date('Y-m-d') }}" autocomplete="off" readonly>
</div>
</div>
</div>
<div class="col-md-2">
<div class="form-group mb-0">
<label class="form-label">&nbsp;</label>
<button type="button" class="btn btn-primary btn-block btn-md" id="btn-filter">
Cari
</button>
</div>
</div>
</div>
{{-- Table Section --}}
<div class="table-responsive">
<table class="table table-striped table-bordered" id="report-stock-products-table">
<thead id="table-header">
<tr>
<th>No</th>
<th>Produk</th>
<th>Kategori</th>
<th>Satuan</th>
<th>Total Stok</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
@endsection
@section('javascripts')
<script>
$(document).ready(function() {
let dealers = [];
let dataTable;
// Loading overlay functions
var showLoadingOverlay = function (message) {
if ($("#loading-overlay").length === 0) {
$("body").append(`
<div id="loading-overlay" style="
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
">
<div class="spinner-border text-light" role="status" style="width: 3rem; height: 3rem;">
<span class="sr-only">Loading...</span>
</div>
<div class="text-light mt-3" style="font-size: 1.1rem; font-weight: 500;">${message}</div>
</div>
`);
} else {
$("#loading-overlay").show();
$("#loading-overlay .text-light:last").text(message);
}
};
var hideLoadingOverlay = function () {
$("#loading-overlay").hide();
};
// Initialize datepicker
$('#filter-date').datepicker({
format: 'yyyy-mm-dd',
autoclose: true,
todayHighlight: true
});
// Show initial loading
showLoadingOverlay("Memuat data dealer...");
// Load dealers first
loadDealers();
function updateTableHeader() {
let headerRow = $('#table-header tr');
// Clear existing dealer columns (keep first 4 columns + total)
headerRow.find('th:gt(3):not(:last)').remove();
// Add dealer columns
dealers.forEach(function(dealer) {
headerRow.find('th:last').before('<th class="text-center" style="min-width: 120px; background-color: #f8f9fa;">' + dealer.name + '</th>');
});
// Update dealer info display
if (dealers.length > 0) {
$('#dealer-names').text(dealers.map(d => d.name).join(', '));
$('#dealer-info').show();
} else {
$('#dealer-info').hide();
}
}
function loadDealers() {
$.ajax({
url: '{{ route("reports.stock-product.dealers") }}',
type: 'GET',
success: function(response) {
dealers = response || [];
showLoadingOverlay("Memuat data produk...");
initializeDataTable();
},
error: function(xhr, status, error) {
console.error('Error loading dealers:', error);
dealers = [];
hideLoadingOverlay();
toastr.error('Gagal memuat data dealer. Silakan refresh halaman.');
// Still try to initialize with empty dealers
showLoadingOverlay("Memuat data produk...");
initializeDataTable();
}
});
}
function initializeDataTable() {
// Destroy existing datatable if exists
if (dataTable) {
dataTable.destroy();
}
// Update table header with dealer columns
updateTableHeader();
// Build dealer columns dynamically
let dealerColumns = dealers.map(function(dealer) {
return {
data: `dealer_${dealer.id}`,
name: `dealer_${dealer.id}`,
title: dealer.name,
orderable: false,
searchable: false,
className: 'text-right',
render: function(data, type, row) {
if (type === 'display') {
return data ? number_format(data, 2) : '-';
}
return data || 0;
}
};
});
// Define all columns
let columns = [
{
data: 'DT_RowIndex',
name: 'DT_RowIndex',
orderable: false,
searchable: false,
width: '50px',
className: 'text-center'
},
{
data: 'product_info',
name: 'product_name',
orderable: true,
searchable: true
},
{
data: 'category_name',
name: 'category_name',
orderable: true,
searchable: true
},
{
data: 'unit',
name: 'unit',
orderable: true,
searchable: false,
width: '80px',
className: 'text-center'
}
];
// Add dealer columns
columns = columns.concat(dealerColumns);
// Add total stock column
columns.push({
data: 'total_stock',
name: 'total_stock',
orderable: true,
searchable: false,
className: 'text-right font-weight-bold',
render: function(data, type, row) {
if (type === 'display') {
return number_format(data, 2);
}
return data;
}
});
// Initialize DataTable
dataTable = $('#report-stock-products-table').DataTable({
destroy: true,
processing: true,
serverSide: false, // We'll handle data loading manually
ajax: {
url: '{{ route("reports.stock-product.data") }}',
type: 'GET',
data: function(d) {
d.filter_date = $('#filter-date').val();
},
beforeSend: function() {
showLoadingOverlay("Memuat data stok produk...");
},
complete: function() {
hideLoadingOverlay();
},
error: function(xhr, error, thrown) {
hideLoadingOverlay();
console.error('DataTable AJAX Error:', error, thrown);
toastr.error('Gagal memuat data. Silakan coba lagi.');
}
},
columns: columns,
order: [[1, 'asc']], // Sort by product name
pageLength: 25,
dom: '<"row"<"col-sm-12 col-md-6"l><"col-sm-12 col-md-6"f>>' +
'<"row"<"col-sm-12"tr>>' +
'<"row"<"col-sm-12 col-md-5"i><"col-sm-12 col-md-7"p>>',
initComplete: function() {
// DataTable initialization complete
hideLoadingOverlay();
// Check if data is empty
if (dataTable.data().length === 0) {
toastr.warning('Tidak ada data stok produk yang ditemukan untuk tanggal yang dipilih.');
}
}
});
}
// Filter button click
$('#btn-filter').click(function() {
if (dataTable) {
// Prevent multiple clicks
var filterButton = $('#btn-filter');
if (filterButton.hasClass('loading')) {
return; // Already processing
}
// Show loading state
filterButton.addClass('loading').prop('disabled', true);
var originalHtml = filterButton.html();
filterButton.html('<i class="fa fa-spinner fa-spin"></i> Mencari...');
// Show loading overlay
showLoadingOverlay("Memuat data stok produk...");
// Reload data
dataTable.ajax.reload(function() {
// Reset button state
filterButton.removeClass('loading').prop('disabled', false).html(originalHtml);
hideLoadingOverlay();
});
}
});
// Export button click
$('#btn-export-excel').click(function() {
exportToExcel();
});
// Enter key on date input
$('#filter-date').keypress(function(e) {
if (e.which == 13) { // Enter key
$('#btn-filter').click();
}
});
// Number format helper
function number_format(number, decimals) {
return new Intl.NumberFormat('id-ID', {
minimumFractionDigits: decimals,
maximumFractionDigits: decimals
}).format(number);
}
// Export to Excel function
function exportToExcel() {
// Prevent multiple clicks
var exportButton = $('#btn-export-excel');
if (exportButton.hasClass('loading')) {
return; // Already processing
}
let filterDate = $('#filter-date').val();
let url = '{{ route("reports.stock-product.export") }}';
if (filterDate) {
url += '?filter_date=' + filterDate;
}
// Show loading state
exportButton.addClass('loading').prop('disabled', true);
var originalHtml = exportButton.html();
exportButton.html('<i class="fa fa-spinner fa-spin"></i> Exporting...');
// Show loading overlay
showLoadingOverlay("Menyiapkan file Excel...");
// Open in new window
let newWindow = window.open(url, '_blank');
// Reset button and hide overlay after a delay
setTimeout(function() {
exportButton.removeClass('loading').prop('disabled', false).html(originalHtml);
hideLoadingOverlay();
}, 2000);
}
// Make exportToExcel available globally
window.exportToExcel = exportToExcel;
// Cleanup loading overlay on page unload
$(window).on('beforeunload', function() {
hideLoadingOverlay();
});
// Fallback for toastr if not available
if (typeof toastr === 'undefined') {
window.toastr = {
success: function(msg) { console.log('SUCCESS:', msg); },
error: function(msg) { console.log('ERROR:', msg); },
warning: function(msg) { console.log('WARNING:', msg); },
info: function(msg) { console.log('INFO:', msg); }
};
}
});
</script>
@endsection