fix feature report stock product
This commit is contained in:
@@ -159,10 +159,7 @@ $(document).ready(function() {
|
||||
// Initialize DataTable
|
||||
$('#kpiTargetsTable').DataTable({
|
||||
"pageLength": 25,
|
||||
"order": [[0, "asc"]],
|
||||
"language": {
|
||||
"url": "//cdn.datatables.net/plug-ins/1.10.24/i18n/Indonesian.json"
|
||||
}
|
||||
"order": [[0, "asc"]]
|
||||
});
|
||||
|
||||
// Auto hide alerts after 5 seconds
|
||||
|
||||
@@ -205,9 +205,9 @@
|
||||
</li>
|
||||
@endcan
|
||||
|
||||
@can('view', $menus['work.index'])
|
||||
@can('view', $menus['reports.stock-product.index'])
|
||||
<li class="kt-menu__item" aria-haspopup="true">
|
||||
<a href="{{ route('work.index') }}" class="kt-menu__link">
|
||||
<a href="{{ route('reports.stock-product.index') }}" class="kt-menu__link">
|
||||
<i class="fa fa-cubes" style="display: flex; align-items: center; margin-right: 10px;"></i>
|
||||
<span class="kt-menu__link-text">Stok Produk</span>
|
||||
</a>
|
||||
|
||||
576
resources/views/reports/stock-products.blade.php
Normal file
576
resources/views/reports/stock-products.blade.php
Normal file
@@ -0,0 +1,576 @@
|
||||
@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"> </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
|
||||
@@ -85,7 +85,7 @@ table.dataTable thead th.sorting:hover:before {
|
||||
<div class="kt-portlet__head-wrapper">
|
||||
<div class="kt-portlet__head-actions">
|
||||
<a href="{{ route('products.export.dealers_stock') }}" class="btn btn-bold btn-success btn--sm" style="margin-right: 8px;">
|
||||
<i class="flaticon2-download"></i>Export Stok Dealer
|
||||
Export
|
||||
</a>
|
||||
@can('create', $menus['products.index'])
|
||||
<a href="{{ route('products.create') }}" class="btn btn-bold btn-label-brand btn--sm">Tambah</a>
|
||||
|
||||
@@ -27,14 +27,13 @@ input.datepicker {
|
||||
|
||||
@section('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">
|
||||
Histori Stock
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="kt-portlet__head kt-portlet__head--lg">
|
||||
<div class="kt-portlet__head-label">
|
||||
<h3 class="kt-portlet__head-title">
|
||||
Histori Stock
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kt-portlet__body">
|
||||
<!-- Filter Section -->
|
||||
|
||||
Reference in New Issue
Block a user