Files
CKB/resources/views/reports/technician.blade.php

1401 lines
56 KiB
PHP

@extends('layouts.backapp')
@section('styles')
<style>
/* Loading overlay - Modified to only cover content section */
#loading-overlay {
position: absolute;
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); }
}
/* Make content section relative for absolute positioning of overlay */
.kt-content {
position: relative;
}
/* 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;
}
/* Mechanic column styling */
.mechanic-column {
background-color: #f8f9fa;
min-width: 120px;
text-align: center;
}
/* Form label styling */
.form-label {
font-weight: 600;
color: #595d6e;
margin-bottom: 8px;
font-size: 13px;
text-transform: none;
letter-spacing: normal;
display: block;
}
/* Filter buttons styling */
.filter-buttons {
display: flex;
gap: 10px;
}
.filter-buttons .btn {
white-space: nowrap;
}
/* Datepicker styling */
.datepicker {
width: 100% !important;
max-width: 100%;
}
.datepicker-dropdown {
width: auto !important;
min-width: 250px;
max-width: 300px;
}
/* Ensure input field follows parent width */
input.datepicker {
width: 100% !important;
box-sizing: border-box;
}
</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 Teknisi
</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">
<div class="col-md-3">
<div class="form-group">
<label class="form-label">Tanggal Mulai</label>
<input type="text" class="form-control datepicker" id="filter-start-date" placeholder="Tanggal awal" value="{{ date('Y-m-d', strtotime('-30 days')) }}" readonly>
</div>
</div>
<div class="col-md-3">
<div class="form-group">
<label class="form-label">Tanggal Akhir</label>
<input type="text" class="form-control datepicker" id="filter-end-date" placeholder="Tanggal akhir" value="{{ date('Y-m-d') }}" readonly>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label class="form-label">Filter Dealer</label>
<select class="form-control select2" id="filter-dealer">
<option value="">Semua Dealer</option>
</select>
</div>
</div>
</div>
<div class="row mb-3">
<div class="col-md-12 filter-buttons">
<button type="button" class="btn btn-primary btn-sm" id="btn-filter">
Cari
</button>
<button type="button" class="btn btn-secondary btn-sm" id="btn-reset">
Reset
</button>
</div>
</div>
<div class="row mb-3">
<div class="col-md-12">
<small class="form-text text-muted" id="default-dealer-info">Filter diset ke "Semua Dealer" untuk melihat data lengkap. Pilih dealer untuk memfilter data.</small>
</div>
</div>
{{-- Table Section --}}
<div class="table-responsive">
<table class="table table-striped table-bordered" id="report-technician-table">
<thead id="table-header">
<tr>
<th>No</th>
<th>Nama Pekerjaan</th>
<th>Kode Pekerjaan</th>
<th>Kategori</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot id="table-footer">
<tr>
<th colspan="4" class="text-center">Total</th>
</tr>
</tfoot>
</table>
</div>
</div>
</div>
</div>
@endsection
@section('javascripts')
<script>
$(document).ready(function() {
// Check if jQuery and DataTable are available
if (typeof $ === 'undefined') {
console.error('jQuery is not loaded');
return;
}
if (typeof $.fn.DataTable === 'undefined') {
console.error('DataTable is not loaded');
// Show error message to user
$('body').append('<div class="alert alert-danger m-3">DataTable library tidak dimuat. Silakan refresh halaman.</div>');
return;
}
let dataTable;
let mechanics = [];
let isInitialized = false; // Flag to prevent double initialization
// Loading overlay functions - Modified to only cover content section
var showLoadingOverlay = function (message) {
const $content = $('#kt_content');
if ($("#loading-overlay").length === 0) {
$content.append(`
<div id="loading-overlay" style="
position: absolute;
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 datepickers
$('.datepicker').datepicker({
format: 'yyyy-mm-dd',
autoclose: true,
todayHighlight: true
});
// Initialize Select2 for dealer dropdown
function initializeSelect2() {
console.log("Initializing Select2...");
if (typeof $.fn.select2 !== 'undefined') {
$('#filter-dealer').select2({
placeholder: 'Semua Dealer',
allowClear: true,
width: '100%'
});
} else {
console.warn('Select2 is not loaded, using default dropdown');
}
}
// Store original button text
$('#btn-filter').data('original-text', $('#btn-filter').html());
// Load dealers first
loadDealers();
function loadDealers() {
$.ajax({
url: '{{ route("reports.technician.dealers") }}',
type: 'GET',
success: function(response) {
console.log('Dealers response:', response);
if (response.status === 'success') {
let options = '<option value="">Semua Dealer</option>';
response.data.forEach(function(dealer) {
options += `<option value="${dealer.id}">${dealer.name} (${dealer.dealer_code})</option>`;
});
$('#filter-dealer').html(options);
console.log('Dealers loaded:', response.data.length);
// Initialize Select2 after populating options
initializeSelect2();
// Set default dealer if provided
if (response.default_dealer && response.data.length > 0) {
// Don't set default dealer - always show "Semua Dealer" on initial load
$('#default-dealer-info').text('Filter diset ke "Semua Dealer" untuk melihat data lengkap. Pilih dealer untuk memfilter data.');
// Load initial data without dealer filter (show all dealers)
if (!isInitialized) {
console.log('Default dealer available but loading with "Semua Dealer" filter');
loadInitialMechanics();
}
} else if (response.default_dealer === null && response.data.length > 0) {
// Admin without default dealer - show all dealers without selection
$('#default-dealer-info').text('Filter diset ke "Semua Dealer" untuk melihat data lengkap. Pilih dealer untuk memfilter data.');
// Load initial data without dealer filter
if (!isInitialized) {
console.log('Admin without default dealer, loading initial data');
loadInitialMechanics();
}
} else if (response.data.length > 0) {
$('#default-dealer-info').text('Filter diset ke "Semua Dealer" untuk melihat data lengkap. Pilih dealer untuk memfilter data.');
// Load initial data without dealer filter
if (!isInitialized) {
console.log('No default dealer, loading initial data');
loadInitialMechanics();
}
} else {
// No dealers available
$('#default-dealer-info').text('Tidak ada dealer yang tersedia untuk Anda.');
hideLoadingOverlay();
}
// Check if user has access to any dealers
if (response.data.length === 0) {
toastr.warning('Anda tidak memiliki akses ke dealer manapun. Silakan hubungi administrator.');
$('#default-dealer-info').text('Anda tidak memiliki akses ke dealer manapun.');
hideLoadingOverlay();
return;
}
} else {
toastr.error('Gagal memuat data dealer');
hideLoadingOverlay();
}
},
error: function() {
toastr.error('Gagal memuat data dealer');
hideLoadingOverlay();
}
});
}
function loadDataWithDealer(dealerId) {
// Show loading overlay
showLoadingOverlay("Memuat data laporan teknisi...");
const startDate = $('#filter-start-date').val();
const endDate = $('#filter-end-date').val();
console.log('Loading data with dealer:', dealerId, 'date range:', { startDate, endDate });
// Get mechanics first, then initialize DataTable
$.ajax({
url: '{{ route("reports.technician.data") }}',
type: 'GET',
data: { dealer_id: dealerId, start_date: startDate, end_date: endDate },
success: function(response) {
console.log('Data response:', response);
if (response.status === 'success' && Array.isArray(response.mechanics)) {
mechanics = response.mechanics;
console.log('Data loaded with dealer:', dealerId, 'mechanics:', mechanics.length);
// Check if user has access to any data
if (response.data.length === 0 && response.mechanics.length === 0) {
console.log('No data available for dealer:', dealerId);
toastr.info('Tidak ada data yang tersedia untuk dealer yang dipilih. Silakan pilih dealer lain.');
$('#default-dealer-info').text('Tidak ada data yang tersedia untuk dealer yang dipilih.');
hideLoadingOverlay();
return;
}
// Now initialize DataTable with mechanics ready
updateTableStructureWithMechanics();
isInitialized = true;
} else {
console.warn('Invalid mechanics data, using fallback');
mechanics = [];
updateTableStructureWithMechanics();
isInitialized = true;
}
},
error: function(xhr, status, error) {
console.error('Error loading data with dealer:', error);
console.error('Response:', xhr.responseText);
toastr.error('Gagal memuat data untuk dealer yang dipilih. Silakan coba lagi.');
hideLoadingOverlay();
// Don't try fallback to avoid multiple notifications
mechanics = [];
updateTableStructureWithMechanics();
isInitialized = true;
}
});
}
function loadInitialMechanics() {
// Get initial filter values (empty dealer, default dates)
let dealerId = '';
const startDate = $('#filter-start-date').val();
const endDate = $('#filter-end-date').val();
console.log('Loading initial mechanics without dealer filter, date range:', { startDate, endDate });
// Show loading overlay
showLoadingOverlay("Memuat data laporan teknisi...");
// Get mechanics first, then initialize DataTable
$.ajax({
url: '{{ route("reports.technician.data") }}',
type: 'GET',
data: { dealer_id: dealerId, start_date: startDate, end_date: endDate },
success: function(response) {
console.log('Initial data response:', response);
if (response.status === 'success' && Array.isArray(response.mechanics)) {
mechanics = response.mechanics;
console.log('Initial mechanics loaded:', mechanics.length);
// Check if user has access to any data
if (response.data.length === 0 && response.mechanics.length === 0) {
console.log('No data available without dealer filter');
// Don't show notification for initial load without dealer filter
$('#default-dealer-info').text('Tidak ada data yang tersedia. Silakan pilih dealer untuk memfilter data.');
hideLoadingOverlay();
return;
}
// Now initialize DataTable with mechanics ready
updateTableStructureWithMechanics();
isInitialized = true;
} else {
console.warn('Invalid mechanics data, using fallback');
mechanics = [];
updateTableStructureWithMechanics();
isInitialized = true;
}
},
error: function(xhr, status, error) {
console.error('Error loading initial mechanics:', error);
console.error('Response:', xhr.responseText);
toastr.error('Gagal memuat data laporan teknisi. Silakan coba lagi.');
hideLoadingOverlay();
console.warn('Error loading initial mechanics, using fallback');
mechanics = [];
updateTableStructureWithMechanics();
isInitialized = true;
}
});
}
function loadTechnicianData() {
console.log('loadTechnicianData called');
// Get dealer value from Select2 or regular select
let dealerId = '';
if (typeof $.fn.select2 !== 'undefined' && $('#filter-dealer').data('select2')) {
dealerId = $('#filter-dealer').select2('val') || '';
console.log('Using Select2 value:', dealerId);
} else {
dealerId = $('#filter-dealer').val() || '';
console.log('Using regular select value:', dealerId);
}
const startDate = $('#filter-start-date').val();
const endDate = $('#filter-end-date').val();
console.log('Date range:', { startDate, endDate });
const requestData = {
dealer_id: dealerId || '',
start_date: startDate,
end_date: endDate
};
console.log('Requesting data with params:', requestData);
$.ajax({
url: '{{ route("reports.technician.data") }}',
type: 'GET',
data: requestData,
success: function(response) {
if (response.status === 'success') {
// Validate response data
if (response.mechanics && Array.isArray(response.mechanics)) {
mechanics = response.mechanics;
} else {
console.warn('Invalid mechanics data:', response.mechanics);
mechanics = [];
}
// Check if user has access to any data
if (response.data.length === 0 && response.mechanics.length === 0) {
toastr.info('Tidak ada data yang tersedia untuk filter yang dipilih atau Anda tidak memiliki akses ke data tersebut.');
$('#default-dealer-info').text('Tidak ada data yang tersedia untuk filter yang dipilih.');
}
} else {
console.error('Server returned error status:', response);
toastr.error('Gagal memuat data laporan teknisi');
}
},
error: function(xhr, status, error) {
console.error('AJAX Error occurred:');
console.error('Status:', status);
console.error('Error:', error);
console.error('Response Text:', xhr.responseText);
console.error('Status Code:', xhr.status);
console.error('Status Text:', xhr.statusText);
let errorMessage = 'Gagal memuat data laporan teknisi';
if (xhr.status === 500) {
errorMessage = 'Server error: ' + (xhr.responseText || 'Unknown error');
} else if (xhr.status === 404) {
errorMessage = 'Endpoint tidak ditemukan';
} else if (xhr.status === 403) {
errorMessage = 'Akses ditolak - Anda tidak memiliki izin untuk mengakses data ini';
}
toastr.error(errorMessage);
},
complete: function() {
// Always reset button state and hide overlay
hideLoadingOverlay();
// Reset button state using stored original text
var $btnFilter = $('#btn-filter');
var storedText = $btnFilter.data('original-text') || 'Cari';
$btnFilter.html(storedText).prop('disabled', false).removeClass('loading');
}
});
}
function initializeDataTable() {
try {
// Destroy existing DataTable if exists and clean up
if (dataTable && typeof dataTable.destroy === 'function') {
dataTable.destroy();
dataTable = null;
}
// Clean up existing search inputs
$('#datatable-search').closest('.row').remove();
$('#simple-search').closest('.row').remove();
// Completely recreate table structure to avoid DOM conflicts
const $table = $('#report-technician-table');
$table.empty();
// Recreate basic table structure
$table.html(`
<thead id="table-header">
<tr>
<th>No</th>
<th>Nama Pekerjaan</th>
<th>Kode Pekerjaan</th>
<th>Kategori</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot id="table-footer">
<tr>
<th colspan="4" class="text-center">Total</th>
</tr>
</tfoot>
`);
// Check if DataTable is available
if (typeof $.fn.DataTable === 'undefined') {
console.error('DataTable is not loaded');
return;
}
// Check if table element exists
if ($table.length === 0) {
console.error('Table element not found');
return;
}
// Get filter values
let dealerId = '';
if (typeof $.fn.select2 !== 'undefined' && $('#filter-dealer').data('select2')) {
dealerId = $('#filter-dealer').select2('val') || '';
} else {
dealerId = $('#filter-dealer').val() || '';
}
const startDate = $('#filter-start-date').val();
const endDate = $('#filter-end-date').val();
// Try to initialize DataTable with Yajra DataTable
try {
// If mechanics are already available, use the complete structure
if (Array.isArray(mechanics) && mechanics.length > 0) {
updateTableStructureWithMechanics();
return;
}
dataTable = $table.DataTable({
processing: true,
serverSide: false, // We'll handle server-side manually
ajax: {
url: '{{ route("reports.technician.datatable") }}',
type: 'GET',
data: function(d) {
d.dealer_id = dealerId;
d.start_date = startDate;
d.end_date = endDate;
return d;
},
dataSrc: function(json) {
console.log('DataTable response:', json);
// Check for error response
if (json.error) {
console.error('DataTable error:', json.error);
toastr.error('Error loading data: ' + json.error);
return [];
}
// Update mechanics from response
if (json.mechanics && Array.isArray(json.mechanics)) {
mechanics = json.mechanics;
console.log('Mechanics loaded:', mechanics.length);
// Update table structure with mechanic columns
setTimeout(function() {
updateTableStructureWithMechanics();
}, 100);
}
return json.data || [];
},
error: function(xhr, error, thrown) {
console.error('DataTable AJAX error:', {
xhr: xhr,
error: error,
thrown: thrown
});
let errorMessage = 'Gagal memuat data';
if (xhr.responseJSON && xhr.responseJSON.error) {
errorMessage = xhr.responseJSON.error;
} else if (xhr.status === 500) {
errorMessage = 'Server error: ' + (xhr.responseText || 'Unknown error');
} else if (xhr.status === 404) {
errorMessage = 'Endpoint tidak ditemukan';
} else if (xhr.status === 403) {
errorMessage = 'Akses ditolak';
}
toastr.error(errorMessage);
hideLoadingOverlay();
}
},
columns: [
{ data: 'DT_RowIndex', name: 'DT_RowIndex', orderable: false, searchable: false, width: '50px', className: 'text-center' },
{ data: 'work_name', name: 'work_name', orderable: true, searchable: true },
{ data: 'work_code', name: 'work_code', orderable: true, searchable: true, width: '120px' },
{ data: 'category_name', name: 'category_name', orderable: true, searchable: true, width: '150px' }
],
pageLength: -1,
lengthMenu: [],
order: [[1, 'asc']],
responsive: false,
dom: '<"row"<"col-sm-12"f>>' +
'<"row"<"col-sm-12"tr>>' +
'<"row"<"col-sm-12"i>>',
initComplete: function() {
try {
var api = this.api();
console.log('DataTable initialization complete');
console.log('Data rows:', api.data().length);
console.log('Mechanics count:', mechanics.length);
if (api.data().length === 0) {
toastr.info('Tidak ada data yang ditemukan untuk filter yang dipilih. Silakan coba filter yang berbeda.');
}
// Enable filter button di sini!
var $btnFilter = $('#btn-filter');
var storedText = $btnFilter.data('original-text') || 'Cari';
$btnFilter.html(storedText).prop('disabled', false).removeClass('loading');
hideLoadingOverlay();
} catch (error) {
console.error('Error in initComplete:', error);
hideLoadingOverlay();
}
},
drawCallback: function() {
try {
// Update totals in footer
updateTableTotals();
} catch (error) {
console.error('Error in drawCallback:', error);
}
}
});
console.log('DataTable initialized successfully');
} catch (dataTableError) {
console.error('DataTable initialization failed:', dataTableError);
toastr.error('Gagal memuat tabel data');
hideLoadingOverlay();
}
} catch (error) {
console.error('Error in initializeDataTable:', error);
toastr.error('Gagal memuat tabel data');
hideLoadingOverlay();
}
}
function createFallbackTable(data) {
try {
// Clean up existing search inputs
$('#datatable-search').closest('.row').remove();
$('#simple-search').closest('.row').remove();
// Ensure DataTable is destroyed
if (dataTable && typeof dataTable.destroy === 'function') {
dataTable.destroy();
dataTable = null;
}
const $table = $('#report-technician-table');
$table.empty();
if (data.length > 0) {
let tableHtml = '<thead><tr>';
tableHtml += '<th>No</th><th>Nama Pekerjaan</th><th>Kode Pekerjaan</th><th>Kategori</th>';
if (Array.isArray(mechanics) && mechanics.length > 0) {
mechanics.forEach(function(mechanic) {
if (mechanic && mechanic.name) {
tableHtml += `<th>${mechanic.name}</th>`;
}
});
}
tableHtml += '</tr></thead><tbody>';
data.forEach(function(item, index) {
tableHtml += `<tr>
<td>${index + 1}</td>
<td>${item.work_name || ''}</td>
<td>${item.work_code || ''}</td>
<td>${item.category_name || ''}</td>
`;
if (Array.isArray(mechanics) && mechanics.length > 0) {
mechanics.forEach(function(mechanic) {
if (mechanic && mechanic.id) {
tableHtml += `<td>${item[`mechanic_${mechanic.id}_total`] || 0}</td>`;
}
});
}
tableHtml += '</tr>';
});
tableHtml += '</tbody>';
$table.html(tableHtml);
// Add simple search functionality to fallback table
addSimpleSearch($table);
} else {
$table.html('<tbody><tr><td colspan="100%" class="text-center">Tidak ada data yang tersedia untuk filter yang dipilih</td></tr></tbody>');
}
console.log('Fallback table created successfully');
hideLoadingOverlay();
} catch (fallbackError) {
console.error('Error creating fallback table:', fallbackError);
toastr.error('Gagal memuat tabel data. Silakan refresh halaman.');
hideLoadingOverlay();
}
}
function addMechanicColumns(api) {
if (!Array.isArray(mechanics) || mechanics.length === 0) {
console.log('No mechanics to add columns for');
return;
}
try {
console.log('Adding mechanic columns for', mechanics.length, 'mechanics');
// Instead of adding columns dynamically, we'll update the table structure
// and reinitialize the DataTable with the complete column configuration
updateTableStructureWithMechanics();
} catch (error) {
console.error('Error adding mechanic columns:', error);
}
}
function updateTableStructureWithMechanics() {
try {
// Destroy existing DataTable
if (dataTable && typeof dataTable.destroy === 'function') {
dataTable.destroy();
dataTable = null;
}
const $table = $('#report-technician-table');
$table.empty();
// Build header rows
let headerRow1 = `
<tr>
<th>No</th>
<th>Nama Pekerjaan</th>
<th>Kode Pekerjaan</th>
<th>Kategori</th>
`;
if (Array.isArray(mechanics) && mechanics.length > 0) {
mechanics.forEach(function(mechanic) {
headerRow1 += `<th class="mechanic-column" style="text-align: center; background-color: #f8f9fa;"><strong>${mechanic.name}</strong></th>`;
});
}
headerRow1 += `</tr>`;
// Set thead
$table.append(`<thead id="table-header">${headerRow1}</thead>`);
$table.append('<tbody></tbody>');
// Build footer
let footerHtml = '<tr><th colspan="4" class="text-center"><strong>Total</strong></th>';
if (Array.isArray(mechanics) && mechanics.length > 0) {
mechanics.forEach(function() {
footerHtml += `<th class="text-center mechanic-column"><strong>0</strong></th>`;
});
}
footerHtml += '</tr>';
$table.append(`<tfoot id="table-footer">${footerHtml}</tfoot>`);
// Reinitialize DataTable
initializeDataTableWithMechanics();
} catch (error) {
console.error('Error updating table structure:', error);
hideLoadingOverlay();
}
}
function initializeDataTableWithMechanics() {
try {
// Get filter values
let dealerId = '';
if (typeof $.fn.select2 !== 'undefined' && $('#filter-dealer').data('select2')) {
dealerId = $('#filter-dealer').select2('val') || '';
} else {
dealerId = $('#filter-dealer').val() || '';
}
const startDate = $('#filter-start-date').val();
const endDate = $('#filter-end-date').val();
// Build complete column configuration
const columns = [
{ data: 'DT_RowIndex', name: 'DT_RowIndex', orderable: false, searchable: false, width: '50px', className: 'text-center' },
{ data: 'work_name', name: 'work_name', orderable: true, searchable: true },
{ data: 'work_code', name: 'work_code', orderable: true, searchable: true, width: '120px' },
{ data: 'category_name', name: 'category_name', orderable: true, searchable: true, width: '150px' }
];
// Add mechanic columns
if (Array.isArray(mechanics) && mechanics.length > 0) {
mechanics.forEach(function(mechanic) {
if (mechanic && mechanic.id) {
columns.push({
data: `mechanic_${mechanic.id}_total`,
name: `mechanic_${mechanic.id}_total`,
title: `${mechanic.name || 'Unknown'}`,
orderable: false,
searchable: false,
className: 'text-center mechanic-column',
defaultContent: '0'
});
}
});
}
// Initialize DataTable with complete configuration
dataTable = $('#report-technician-table').DataTable({
processing: true,
serverSide: false,
ajax: {
url: '{{ route("reports.technician.datatable") }}',
type: 'GET',
data: function(d) {
d.dealer_id = dealerId;
d.start_date = startDate;
d.end_date = endDate;
return d;
},
dataSrc: function(json) {
console.log('DataTable response:', json);
// Check for error response
if (json.error) {
console.error('DataTable error:', json.error);
toastr.error('Error loading data: ' + json.error);
return [];
}
// Don't update mechanics from datatable response - use existing mechanics
// if (json.mechanics && Array.isArray(json.mechanics)) {
// mechanics = json.mechanics;
// }
return json.data || [];
},
error: function(xhr, error, thrown) {
console.error('DataTable AJAX error:', {
xhr: xhr,
error: error,
thrown: thrown
});
let errorMessage = 'Gagal memuat data';
if (xhr.responseJSON && xhr.responseJSON.error) {
errorMessage = xhr.responseJSON.error;
} else if (xhr.status === 500) {
errorMessage = 'Server error: ' + (xhr.responseText || 'Unknown error');
} else if (xhr.status === 404) {
errorMessage = 'Endpoint tidak ditemukan';
} else if (xhr.status === 403) {
errorMessage = 'Akses ditolak';
}
toastr.error(errorMessage);
hideLoadingOverlay();
}
},
columns: columns,
pageLength: -1,
lengthMenu: [],
order: [[1, 'asc']],
responsive: false,
dom: '<"row"<"col-sm-12"f>>' +
'<"row"<"col-sm-12"tr>>' +
'<"row"<"col-sm-12"i>>',
initComplete: function() {
try {
var api = this.api();
console.log('DataTable with mechanics initialization complete');
console.log('Data rows:', api.data().length);
console.log('Mechanics count:', mechanics.length);
if (api.data().length === 0) {
toastr.info('Tidak ada data yang ditemukan untuk filter yang dipilih. Silakan coba filter yang berbeda.');
} else {
// Update totals after initialization
setTimeout(function() {
updateTableTotals();
}, 200);
}
// Enable filter button di sini!
var $btnFilter = $('#btn-filter');
var storedText = $btnFilter.data('original-text') || 'Cari';
$btnFilter.html(storedText).prop('disabled', false).removeClass('loading');
hideLoadingOverlay();
} catch (error) {
console.error('Error in initComplete:', error);
hideLoadingOverlay();
}
},
drawCallback: function() {
try {
console.log('DataTable drawCallback triggered');
// Add a small delay to ensure data is fully rendered
setTimeout(function() {
updateTableTotals();
}, 100);
} catch (error) {
console.error('Error in drawCallback:', error);
}
}
});
console.log('DataTable with mechanics initialized successfully');
} catch (error) {
console.error('Error initializing DataTable with mechanics:', error);
toastr.error('Gagal memuat tabel data');
hideLoadingOverlay();
}
}
function updateTableTotals() {
if (!dataTable) {
console.log('DataTable not initialized');
return;
}
try {
// Get DataTable API - try multiple methods
let api = null;
// Method 1: Direct API access
if (dataTable && typeof dataTable.api === 'function') {
api = dataTable.api();
console.log('Using dataTable.api() method');
}
// Method 2: jQuery DataTable API
else if (dataTable && typeof dataTable.dataTable === 'function') {
api = dataTable.dataTable().api();
console.log('Using dataTable.dataTable().api() method');
}
// Method 3: Direct access to DataTable instance
else if (dataTable && dataTable.api) {
api = dataTable.api;
console.log('Using direct dataTable.api access');
}
// Method 4: Try to get from jQuery element
else if ($('#report-technician-table').dataTable) {
api = $('#report-technician-table').dataTable().api();
console.log('Using jQuery element dataTable().api() method');
}
if (!api) {
console.warn('Cannot access DataTable API - trying alternative method');
// Alternative: Get data directly from table rows
updateTableTotalsFromDOM();
return;
}
console.log('DataTable API accessed successfully');
const data = api.data().toArray();
console.log('Data rows found:', data.length);
if (data.length === 0) {
console.log('No data rows found');
return;
}
// Calculate totals for each mechanic
const totals = {};
if (Array.isArray(mechanics) && mechanics.length > 0) {
mechanics.forEach(function(mechanic) {
if (mechanic && mechanic.id) {
totals[`mechanic_${mechanic.id}_total`] = 0;
}
});
}
// Sum up values from all rows
data.forEach(function(row, index) {
if (Array.isArray(mechanics) && mechanics.length > 0) {
mechanics.forEach(function(mechanic) {
if (mechanic && mechanic.id) {
const value = parseInt(row[`mechanic_${mechanic.id}_total`] || 0);
totals[`mechanic_${mechanic.id}_total`] += value;
}
});
}
});
console.log('Calculated totals:', totals);
// Update footer with totals
const $footer = $('#table-footer tr');
if ($footer.length > 0) {
let footerHtml = '<th colspan="4" class="text-center"><strong>Total</strong></th>';
if (Array.isArray(mechanics) && mechanics.length > 0) {
mechanics.forEach(function(mechanic) {
if (mechanic && mechanic.id) {
const total = totals[`mechanic_${mechanic.id}_total`] || 0;
footerHtml += `<th class="text-center mechanic-column"><strong>${total}</strong></th>`;
}
});
}
$footer.html(footerHtml);
console.log('Footer updated successfully');
} else {
console.warn('Footer row not found');
}
} catch (error) {
console.error('Error updating table totals:', error);
// Fallback to DOM method
updateTableTotalsFromDOM();
}
}
function updateTableTotalsFromDOM() {
try {
console.log('Using DOM method to calculate totals');
// Calculate totals for each mechanic
const totals = {};
if (Array.isArray(mechanics) && mechanics.length > 0) {
mechanics.forEach(function(mechanic) {
if (mechanic && mechanic.id) {
totals[`mechanic_${mechanic.id}_total`] = 0;
}
});
}
// Get data from table rows directly
const $rows = $('#report-technician-table tbody tr');
console.log('Found table rows:', $rows.length);
$rows.each(function(index) {
const $row = $(this);
const $cells = $row.find('td');
if (Array.isArray(mechanics) && mechanics.length > 0) {
mechanics.forEach(function(mechanic, mechanicIndex) {
if (mechanic && mechanic.id) {
// Skip first 4 columns (No, Nama Pekerjaan, Kode Pekerjaan, Kategori)
const cellIndex = 4 + mechanicIndex;
const $cell = $cells.eq(cellIndex);
const value = parseInt($cell.text() || 0);
totals[`mechanic_${mechanic.id}_total`] += value;
}
});
}
});
console.log('DOM calculated totals:', totals);
// Update footer with totals
const $footer = $('#table-footer tr');
if ($footer.length > 0) {
let footerHtml = '<th colspan="4" class="text-center"><strong>Total</strong></th>';
if (Array.isArray(mechanics) && mechanics.length > 0) {
mechanics.forEach(function(mechanic) {
if (mechanic && mechanic.id) {
const total = totals[`mechanic_${mechanic.id}_total`] || 0;
footerHtml += `<th class="text-center mechanic-column"><strong>${total}</strong></th>`;
}
});
}
$footer.html(footerHtml);
console.log('DOM Footer updated successfully');
}
} catch (error) {
console.error('Error in DOM method:', error);
}
}
function addSimpleSearch($table) {
// Add search input above table
const searchHtml = `
<div class="row mb-3">
<div class="col-sm-12">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text">
<i class="fa fa-search"></i>
</span>
</div>
<input type="text" class="form-control" id="simple-search" placeholder="Cari data...">
</div>
</div>
</div>
`;
$table.before(searchHtml);
// Add search functionality
$('#simple-search').on('keyup', function() {
const searchTerm = $(this).val().toLowerCase();
$table.find('tbody tr').each(function() {
const rowText = $(this).text().toLowerCase();
if (rowText.includes(searchTerm)) {
$(this).show();
} else {
$(this).hide();
}
});
});
}
// Filter button click - Fixed button state management
$('#btn-filter').on('click', function() {
console.log('Filter button clicked');
// Prevent multiple clicks
var filterButton = $('#btn-filter');
if (filterButton.hasClass('loading') || filterButton.prop('disabled')) {
console.log('Button already loading or disabled, ignoring click');
return; // Already processing
}
// Get dealer value from Select2 or regular select
let selectedDealer = '';
if (typeof $.fn.select2 !== 'undefined' && $('#filter-dealer').data('select2')) {
selectedDealer = $('#filter-dealer').select2('val') || '';
} else {
selectedDealer = $('#filter-dealer').val();
}
console.log('Selected dealer:', selectedDealer);
// Show warning if user selects "Semua Dealer"
if (selectedDealer === '') {
if (!confirm('Memilih "Semua Dealer" akan memuat data yang lebih besar dan mungkin memakan waktu lebih lama. Lanjutkan?')) {
return; // User cancelled
}
}
console.log('Filter button clicked - starting data load');
// Store the original button text before changing it
var originalText = filterButton.html();
filterButton.data('original-text', originalText);
// Show loading state
filterButton.addClass('loading').prop('disabled', true);
filterButton.html('<i class="fa fa-spinner fa-spin"></i> Mencari...');
console.log('Button loading state set, original text:', originalText);
console.log('Button disabled state:', filterButton.prop('disabled'));
console.log('Button loading class:', filterButton.hasClass('loading'));
// Show loading overlay
showLoadingOverlay("Memuat data laporan teknisi...");
// Reload DataTable
if (dataTable && typeof dataTable.destroy === 'function') {
dataTable.destroy();
dataTable = null;
}
// Reset initialization flag to allow re-initialization
isInitialized = false;
// Get mechanics first, then initialize DataTable
let dealerId = '';
if (typeof $.fn.select2 !== 'undefined' && $('#filter-dealer').data('select2')) {
dealerId = $('#filter-dealer').select2('val') || '';
} else {
dealerId = $('#filter-dealer').val();
}
const startDate = $('#filter-start-date').val();
const endDate = $('#filter-end-date').val();
$.ajax({
url: '{{ route("reports.technician.data") }}',
type: 'GET',
data: { dealer_id: dealerId, start_date: startDate, end_date: endDate },
success: function(response) {
if (response.status === 'success' && Array.isArray(response.mechanics)) {
mechanics = response.mechanics;
console.log('Filter mechanics loaded:', mechanics.length);
updateTableStructureWithMechanics();
} else {
console.warn('Invalid mechanics data, using fallback');
mechanics = [];
updateTableStructureWithMechanics();
}
},
error: function() {
console.warn('Error loading mechanics, using fallback');
mechanics = [];
updateTableStructureWithMechanics();
}
});
});
// Export button click
$('#btn-export-excel').on('click', function() {
exportToExcel();
});
// Reset button click
$('#btn-reset').on('click', function() {
console.log('Reset button clicked');
// Reset dealer selection to "Semua Dealer"
if (typeof $.fn.select2 !== 'undefined' && $('#filter-dealer').data('select2')) {
$('#filter-dealer').val('').trigger('change');
} else {
$('#filter-dealer').val('');
}
// Reset date inputs
$('#filter-start-date').val('{{ date('Y-m-d', strtotime('-30 days')) }}');
$('#filter-end-date').val('{{ date('Y-m-d') }}');
console.log('Filters reset to "Semua Dealer", reloading DataTable...');
// Reset initialization flag
isInitialized = false;
// Show loading overlay
showLoadingOverlay("Memuat data laporan teknisi...");
if (dataTable && typeof dataTable.destroy === 'function') {
dataTable.destroy();
dataTable = null;
}
// Get mechanics first, then initialize DataTable
let dealerId = '';
const startDate = $('#filter-start-date').val();
const endDate = $('#filter-end-date').val();
$.ajax({
url: '{{ route("reports.technician.data") }}',
type: 'GET',
data: { dealer_id: dealerId, start_date: startDate, end_date: endDate },
success: function(response) {
if (response.status === 'success' && Array.isArray(response.mechanics)) {
mechanics = response.mechanics;
console.log('Reset mechanics loaded:', mechanics.length);
updateTableStructureWithMechanics();
} else {
console.warn('Invalid mechanics data, using fallback');
mechanics = [];
updateTableStructureWithMechanics();
}
},
error: function() {
console.warn('Error loading mechanics, using fallback');
mechanics = [];
updateTableStructureWithMechanics();
}
});
});
function exportToExcel() {
// Prevent multiple clicks
var exportButton = $('#btn-export-excel');
if (exportButton.hasClass('loading')) {
return; // Already processing
}
// Get dealer value from Select2 or regular select
let dealerId = '';
if (typeof $.fn.select2 !== 'undefined' && $('#filter-dealer').data('select2')) {
dealerId = $('#filter-dealer').select2('val') || '';
} else {
dealerId = $('#filter-dealer').val();
}
const startDate = $('#filter-start-date').val();
const endDate = $('#filter-end-date').val();
let url = '{{ route("reports.technician.export") }}';
let params = [];
if (dealerId) {
params.push('dealer_id=' + dealerId);
}
if (startDate) {
params.push('start_date=' + startDate);
}
if (endDate) {
params.push('end_date=' + endDate);
}
if (params.length > 0) {
url += '?' + params.join('&');
}
// Show loading state
exportButton.addClass('loading').prop('disabled', true);
var originalHtml = exportButton.html();
exportButton.html('<i class="fa fa-spinner fa-spin"></i> Exporting...');
// 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();
});
// Cleanup Select2 on page unload
$(window).on('beforeunload', function() {
if (typeof $.fn.select2 !== 'undefined' && $('#filter-dealer').data('select2')) {
$('#filter-dealer').select2('destroy');
}
});
// 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