993 lines
56 KiB
PHP
Executable File
993 lines
56 KiB
PHP
Executable File
@extends('m3::liberty-theme.layout.main')
|
|
@section('title')
|
|
Exec Summary - Line Chart
|
|
@endsection
|
|
|
|
{{-- Flat Picker CSS --}}
|
|
<link rel="stylesheet" href="{{ route('m3.assets', ['path' => 'liberty-theme/vendor/flatpickr-4.6.13.min.css']) }}">
|
|
<link rel="stylesheet" href="{{ route('m3.assets', ['path' => 'liberty-theme/vendor/flatpickr-monthSelect-4.6.13.css']) }}">
|
|
|
|
@section('content')
|
|
<div class="container-fluid px-4">
|
|
<h1 class="mt-4">Exec Summary - Line Chart</h1>
|
|
<div class="tab-content" id="pills-tabContent">
|
|
<div class="tab-pane fade show active" id="pills-home" role="tabpanel" aria-labelledby="pills-home-tab">
|
|
<div class="card mb-4">
|
|
{{-- <div class="card-header"></div> --}}
|
|
<div class="card-body">
|
|
<div class="row">
|
|
{{-- Selectors --}}
|
|
<div class="col-2 border-end">
|
|
<form id="chartSelectorsForm">
|
|
<h6>Sales / Weight / Lines</h6>
|
|
|
|
{{-- Warehouses --}}
|
|
<div class="mb-3">
|
|
<div class="form-check-container">
|
|
@foreach ($IC670M as $key => $IC670MR)
|
|
@php $currentWHDDA = trim($IC670MR->WDCO).trim($IC670MR->WDPL).trim($IC670MR->WDWHS); @endphp
|
|
<div class="form-check">
|
|
<input class="form-check-input" type="checkbox" name="WHDDA[]"
|
|
id="WHDDA_{{ $currentWHDDA }}" value="{{ $currentWHDDA }}"
|
|
data-comp="{{ trim($IC670MR->WDCO) }}"
|
|
data-pl="{{ trim($IC670MR->WDPL) }}"
|
|
data-wh="{{ trim($IC670MR->WDWHS) }}"
|
|
{{ $WHDDA != '' && $WHDDA == $currentWHDDA ? 'checked' : '' }}>
|
|
<label class="form-check-label" for="WHDDA_{{ $currentWHDDA }}">
|
|
{{ trim(Str::upper($IC670MR->WDWNAM)) }}
|
|
</label>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
{{-- Report Period --}}
|
|
<div class="mb-3">
|
|
<div class="btn-group" aria-label="Report Period">
|
|
<input type="radio" class="btn-check" name="reportPeriod" id="periodMonthly" value="monthly" checked>
|
|
<label class="btn btn-outline-primary btn-sm" for="periodMonthly">Monthly</label>
|
|
|
|
<input type="radio" class="btn-check" name="reportPeriod" id="periodQuarter" value="quarter">
|
|
<label class="btn btn-outline-primary btn-sm" for="periodQuarter">Quarter</label>
|
|
|
|
<input type="radio" class="btn-check" name="reportPeriod" id="periodYear" value="year">
|
|
<label class="btn btn-outline-primary btn-sm" for="periodYear">Year</label>
|
|
|
|
<input type="radio" class="btn-check" name="reportPeriod" id="preiodCustom" value="custom">
|
|
<label class="btn btn-outline-primary btn-sm" for="preiodCustom">Daily</label>
|
|
</div>
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
{{-- From To Dates --}}
|
|
<div class="mb-3">
|
|
@php
|
|
$todayDate = today()->format('Ym');
|
|
$reportDate = \DateTime::createFromFormat('Ym', $todayDate)
|
|
->modify('first day of January last year')
|
|
->format('Y-m');
|
|
$toreportDate = \DateTime::createFromFormat('Ym', $todayDate)->format(
|
|
'Y-m',
|
|
);
|
|
@endphp
|
|
<div class="row">
|
|
{{-- <fieldset id="fromToDates"> --}}
|
|
<div class="row align-items-center mb-2">
|
|
<div class="col-md-4">
|
|
<label class="col-form-label" for="REPORTDATE">From</label>
|
|
</div>
|
|
<div class="col-md-8">
|
|
<input type="text" class="form-control" id="REPORTDATE"
|
|
name="REPORTDATE" value="{{ $reportDate }}">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row align-items-center" id="TOREPORTDATEDIV">
|
|
<div class="col-md-4">
|
|
<label class="col-form-label" for="TOREPORTDATE">To</label>
|
|
</div>
|
|
<div class="col-md-8">
|
|
<input type="text" class="form-control" id="TOREPORTDATE"
|
|
name="TOREPORTDATE" value="{{ $toreportDate }}">
|
|
</div>
|
|
</div>
|
|
{{-- </fieldset> --}}
|
|
</div>
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
{{-- Sales Weight Lines --}}
|
|
<div class="mb-3">
|
|
<div class="form-check form-check-inline">
|
|
<input class="form-check-input" type="checkbox" id="checkSales"
|
|
name="checkSales" checked>
|
|
<label class="form-check-label" for="checkSales">Sales</label>
|
|
</div>
|
|
<div class="form-check form-check-inline">
|
|
<input class="form-check-input" type="checkbox" id="checkWeight"
|
|
name="checkWeight">
|
|
<label class="form-check-label" for="checkWeight">Weight</label>
|
|
</div>
|
|
<div class="form-check form-check-inline">
|
|
<input class="form-check-input" type="checkbox" id="checkLines"
|
|
name="checkLines">
|
|
<label class="form-check-label" for="checkLines">Lines</label>
|
|
</div>
|
|
</div>
|
|
|
|
<hr>
|
|
|
|
<input type="hidden" name="_token" value="{{ csrf_token() }}">
|
|
|
|
<button type="button" class="btn btn-primary mt-4" id="updateCharts">Submit</button>
|
|
</form>
|
|
</div>
|
|
{{-- Charts --}}
|
|
<div class="col-10">
|
|
<div class="container-fluid">
|
|
<canvas id="acquisitions"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
@endsection
|
|
|
|
@section('scripts')
|
|
{{-- Chart JS --}}
|
|
<script src="{{ route('m3.assets', ['path' => 'liberty-theme/vendor/chart-4.4.8.umd.min.js']) }}"></script>
|
|
|
|
<!-- Flatpickr JS -->
|
|
<script src="{{ route('m3.assets', ['path' => 'liberty-theme/vendor/flatpickr-4.6.13.min.js']) }}"></script>
|
|
<!-- Month Select Plugin -->
|
|
<script src="{{ route('m3.assets', ['path' => 'liberty-theme/vendor/flatpickr-monthSelect-4.6.13.js']) }}"></script>
|
|
|
|
<script>
|
|
$(document).ready(function() {
|
|
|
|
//// Init Dates
|
|
let fromMonthPicker = flatpickr("#REPORTDATE", {
|
|
dateFormat: "Y-m", // Format as Year-Month
|
|
plugins: [
|
|
new monthSelectPlugin({
|
|
shorthand: false, // Show short month names (e.g., Jan, Feb)
|
|
readOnly: false,
|
|
dateFormat: "Y-m", // Format output
|
|
altFormat: "F Y", // Display full month name (e.g., January 2025)
|
|
theme: "material_blue" // Options: "light", "dark", "material_blue", etc.
|
|
})
|
|
],
|
|
onChange: function(selectedDates, dateStr) {
|
|
toMonthPicker.set("minDate",
|
|
dateStr); // Prevent selecting a "To" month before "From"
|
|
},
|
|
// Add these explicit styling options
|
|
onReady: function(dateObj, dateStr, instance) {
|
|
instance.input.style.backgroundColor = 'white';
|
|
instance.input.style.cursor = 'pointer';
|
|
}
|
|
});
|
|
|
|
let toMonthPicker = flatpickr("#TOREPORTDATE", {
|
|
dateFormat: "Y-m",
|
|
plugins: [
|
|
new monthSelectPlugin({
|
|
shorthand: true,
|
|
dateFormat: "Y-m",
|
|
altFormat: "F Y",
|
|
theme: "material_blue"
|
|
})
|
|
],
|
|
onChange: function(selectedDates, dateStr) {
|
|
fromMonthPicker.set("maxDate",
|
|
dateStr); // Prevent selecting a "From" month after "To"
|
|
},
|
|
// Add these explicit styling options
|
|
onReady: function(dateObj, dateStr, instance) {
|
|
instance.input.style.backgroundColor = 'white';
|
|
instance.input.style.cursor = 'pointer';
|
|
}
|
|
});
|
|
//// End Dates
|
|
|
|
//// Add change event listener to radio buttons
|
|
$('input[name="reportPeriod"]').change(function() {
|
|
const isDisabled = $(this).val() == 'custom' || $(this).val() == 'monthly';
|
|
// $('#TOREPORTDATE').prop('disabled', isDisabled);
|
|
|
|
if (isDisabled) {
|
|
$('#TOREPORTDATEDIV').hide();
|
|
// toMonthPicker.input.style.backgroundColor = '#e9ecef';
|
|
// toMonthPicker.input.style.cursor = 'not-allowed';
|
|
|
|
// Set fromMonthPicker to today
|
|
const today = new Date();
|
|
fromMonthPicker.setDate(today);
|
|
}
|
|
// Show Todate
|
|
else {
|
|
$('#TOREPORTDATEDIV').show();
|
|
// toMonthPicker.input.style.backgroundColor = 'white';
|
|
// toMonthPicker.input.style.cursor = 'pointer';
|
|
}
|
|
});
|
|
|
|
//// Trigger Period change
|
|
$('input[name="reportPeriod"]').trigger("change");
|
|
|
|
//// Update Charts
|
|
$("#updateCharts").click(function() {
|
|
//// Validate Fields before calling Ajax
|
|
// Validation checks
|
|
let isValid = true;
|
|
let errorMessages = [];
|
|
|
|
// Check if at least one WHDDA checkbox is checked
|
|
const checkedWhdda = $("input[name='WHDDA[]']:checked").length;
|
|
if (checkedWhdda === 0) {
|
|
isValid = false;
|
|
errorMessages.push("Please select at least one Warehouse/Plant");
|
|
}
|
|
|
|
// Get selected period
|
|
const checkedPeriod = $('input[name="reportPeriod"]:checked').val();
|
|
// console.log(checkedPeriod);
|
|
|
|
// Check From and To dates
|
|
const fromDate = $("#REPORTDATE").val();
|
|
const toDate = $("#TOREPORTDATE").val();
|
|
|
|
if ((checkedPeriod == 'monthly' || checkedPeriod == 'custom') && !fromDate) {
|
|
isValid = false;
|
|
errorMessages.push("Please select date");
|
|
}
|
|
else if (!fromDate && !toDate) {
|
|
isValid = false;
|
|
errorMessages.push("Please select both From and To dates");
|
|
}
|
|
|
|
// Check if at least one of Sales/Weight/Lines is checked
|
|
const checkedMetrics =
|
|
$("#checkSales:checked").length +
|
|
$("#checkWeight:checked").length +
|
|
$("#checkLines:checked").length;
|
|
|
|
if (checkedMetrics === 0) {
|
|
isValid = false;
|
|
errorMessages.push("Please select at least one metric (Sales, Weight, or Lines)");
|
|
}
|
|
|
|
// If validation fails, show SweetAlert
|
|
if (!isValid) {
|
|
Swal.fire({
|
|
icon: 'warning',
|
|
title: 'Validation Error',
|
|
html: errorMessages.map(msg => `<p>${msg}</p>`).join(''),
|
|
confirmButtonText: 'OK'
|
|
});
|
|
return;
|
|
}
|
|
|
|
let formData = $('#chartSelectorsForm').serialize(); // Serialize form data
|
|
// console.log(formData); return false;
|
|
|
|
//// Run Ajax
|
|
$.ajax({
|
|
url: "{{ route('m3.execsummaryline') }}",
|
|
type: "POST",
|
|
data: formData,
|
|
success: function(response) {
|
|
//// Update charts
|
|
if (response.success == true) {
|
|
// First: Destroy Chart if it exists
|
|
if (window.execSummaryChart !== undefined) {
|
|
window.execSummaryChart.destroy();
|
|
}
|
|
|
|
checkSales = response.checkSales;
|
|
checkWeight = response.checkWeight;
|
|
checkLines = response.checkLines;
|
|
|
|
dataResult = response.result;
|
|
dataResultLY = response.resultLY; // Last year data
|
|
reportPeriod = response.reportPeriod;
|
|
|
|
// Build Data for Current Year
|
|
const data = dataResult.map(item => ({
|
|
year: item.YEARS,
|
|
dollars: parseInt(item.DOLLARS),
|
|
weight: parseInt(item.WEIGHT),
|
|
lines: parseInt(item.LINES),
|
|
whs: parseInt(item.WHS)
|
|
}));
|
|
|
|
// Build Data for Last Year
|
|
const dataLY = dataResultLY ? dataResultLY.map(item => ({
|
|
year: item.YEARS,
|
|
dollars: parseInt(item.DOLLARS),
|
|
weight: parseInt(item.WEIGHT),
|
|
lines: parseInt(item.LINES),
|
|
whs: parseInt(item.WHS)
|
|
})) : [];
|
|
|
|
// Determine data format (year, quarterly, monthly, or daily)
|
|
let dataFormat;
|
|
if (data.length > 0) {
|
|
const sampleYear = data[0].year;
|
|
if (sampleYear.includes("Q")) {
|
|
dataFormat = "quarterly";
|
|
} else if (sampleYear.match(/^\d{4}-\d{2}$/)) {
|
|
dataFormat = "monthly";
|
|
} else if (/^\d{2}$/.test(sampleYear)) {
|
|
dataFormat = "daily";
|
|
} else {
|
|
dataFormat = "yearly";
|
|
}
|
|
}
|
|
|
|
// Function to generate distinct colors
|
|
function generateDistinctColors(count) {
|
|
let colors = [];
|
|
for (let i = 0; i < count; i++) {
|
|
let hue = (i * 137.5) % 360; // Golden angle for uniqueness
|
|
let saturation = 70 + (i % 3) * 10;
|
|
let lightness = 50 + (i % 2) * 10;
|
|
colors.push(`hsl(${hue}, ${saturation}%, ${lightness}%)`);
|
|
}
|
|
return colors;
|
|
}
|
|
|
|
// Function to create a lighter version of a color for LY data
|
|
function createLighterColor(color) {
|
|
// Extract HSL values
|
|
const match = color.match(/hsl\((\d+\.?\d*),\s*(\d+\.?\d*)%,\s*(\d+\.?\d*)%\)/);
|
|
if (match) {
|
|
const h = parseFloat(match[1]);
|
|
const s = parseFloat(match[2]);
|
|
const l = parseFloat(match[3]);
|
|
|
|
// Create lighter version by increasing lightness and reducing saturation
|
|
return `hsl(${h}, ${Math.max(s - 20, 30)}%, ${Math.min(l + 15, 85)}%)`;
|
|
}
|
|
return color; // Return original if parsing fails
|
|
}
|
|
|
|
// Group data according to format
|
|
let labels = [];
|
|
let datasets = [];
|
|
const whsSet = new Set();
|
|
|
|
// Initialize warehouse set from data
|
|
data.forEach(entry => whsSet.add(entry.whs));
|
|
if (dataLY) dataLY.forEach(entry => whsSet.add(entry.whs));
|
|
const whsArray = Array.from(whsSet).sort((a, b) => a - b);
|
|
|
|
// Monthly data format handling
|
|
if (dataFormat === "monthly") {
|
|
// Extract unique year-month combinations
|
|
const periodData = new Map();
|
|
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
|
|
// Process current year data
|
|
data.forEach(entry => {
|
|
const [year, month] = entry.year.split("-");
|
|
const monthIndex = parseInt(month) - 1;
|
|
const monthName = monthNames[monthIndex];
|
|
const yearSuffix = year.substring(2); // Last 2 digits
|
|
const periodKey = `${monthName}`; // '${yearSuffix}
|
|
const periodKey2 = `${monthName} '${yearSuffix}`;
|
|
|
|
if (!periodData.has(periodKey)) {
|
|
periodData.set(periodKey, { current: {}, ly: {} ,label: periodKey2 });
|
|
}
|
|
|
|
const whsData = periodData.get(periodKey).current;
|
|
if (!whsData[entry.whs]) {
|
|
whsData[entry.whs] = { dollars: 0, weight: 0, lines: 0 };
|
|
}
|
|
|
|
whsData[entry.whs].dollars += entry.dollars;
|
|
whsData[entry.whs].weight += entry.weight;
|
|
whsData[entry.whs].lines += entry.lines;
|
|
});
|
|
|
|
// Process last year data if available
|
|
if (dataLY && dataLY.length > 0) {
|
|
dataLY.forEach(entry => {
|
|
const [year, month] = entry.year.split("-");
|
|
const monthIndex = parseInt(month) - 1;
|
|
const monthName = monthNames[monthIndex];
|
|
const yearSuffix = year.substring(2); // Last 2 digits
|
|
const periodKey = `${monthName}`; // '${yearSuffix}
|
|
const periodKey2 = `${monthName} '${yearSuffix}`;
|
|
|
|
if (!periodData.has(periodKey)) {
|
|
periodData.set(periodKey, { current: {}, ly: {},label: periodKey2 });
|
|
}
|
|
|
|
const whsData = periodData.get(periodKey).ly;
|
|
if (!whsData[entry.whs]) {
|
|
whsData[entry.whs] = { dollars: 0, weight: 0, lines: 0 };
|
|
}
|
|
|
|
whsData[entry.whs].dollars += entry.dollars;
|
|
whsData[entry.whs].weight += entry.weight;
|
|
whsData[entry.whs].lines += entry.lines;
|
|
});
|
|
}
|
|
|
|
// Sort labels chronologically
|
|
labels = Array.from(periodData.keys()).sort((a, b) => {
|
|
// Parse month and year
|
|
const [monthA, yearA] = a.split(" '");
|
|
const [monthB, yearB] = b.split(" '");
|
|
const yearDiff = parseInt(yearA) - parseInt(yearB);
|
|
if (yearDiff !== 0) return yearDiff;
|
|
|
|
// Compare months if same year
|
|
const monthOrder = { "Jan": 1, "Feb": 2, "Mar": 3, "Apr": 4, "May": 5, "Jun": 6,"Jul": 7, "Aug": 8, "Sep": 9, "Oct": 10, "Nov": 11, "Dec": 12 };
|
|
return monthOrder[monthA] - monthOrder[monthB];
|
|
});
|
|
|
|
// Generate colors for each WHS
|
|
const whsColors = {};
|
|
whsArray.forEach((whs, index) => {
|
|
whsColors[whs] = generateDistinctColors(whsArray.length)[index];
|
|
});
|
|
|
|
// Create datasets for each metric and warehouse
|
|
whsArray.forEach(whs => {
|
|
if (checkSales === 'on') {
|
|
// Sales dataset for current year
|
|
datasets.push({
|
|
type: 'line',
|
|
label: `Sales TY (WHS ${whs})`,
|
|
data: labels.map(period => ({
|
|
x: period,
|
|
y: periodData.get(period)?.current[whs]?.dollars || 0,
|
|
label: periodData.get(period)?.label || period
|
|
})),
|
|
borderColor: whsColors[whs],
|
|
backgroundColor: whsColors[whs],
|
|
yAxisID: 'yLeft',
|
|
tension: 0.3,
|
|
fill: false
|
|
});
|
|
|
|
// Sales dataset for last year
|
|
if (dataLY && dataLY.length > 0) {
|
|
datasets.push({
|
|
type: 'line',
|
|
label: `Sales LY (WHS ${whs})`,
|
|
data: labels.map(period => ({
|
|
x: period,
|
|
y: periodData.get(period)?.ly[whs]?.dollars || 0,
|
|
label: periodData.get(period)?.label || period
|
|
})),
|
|
borderColor: createLighterColor(whsColors[whs]),
|
|
backgroundColor: createLighterColor(whsColors[whs]),
|
|
borderDash: [10, 5],
|
|
yAxisID: 'yLeft',
|
|
tension: 0.3,
|
|
fill: false,
|
|
order: 1
|
|
});
|
|
}
|
|
}
|
|
// Weight
|
|
if (checkWeight === 'on') {
|
|
// Weight dataset for current year
|
|
datasets.push({
|
|
type: 'line',
|
|
label: `Weight TY (WHS ${whs})`,
|
|
data: labels.map(period =>
|
|
periodData.get(period)?.current[whs]?.weight || 0
|
|
),
|
|
borderColor: whsColors[whs],
|
|
backgroundColor: whsColors[whs],
|
|
// borderDash: [5, 5],
|
|
yAxisID: 'yLeft',
|
|
tension: 0.3,
|
|
fill: false
|
|
});
|
|
|
|
// Weight dataset for last year
|
|
if (dataLY && dataLY.length > 0) {
|
|
datasets.push({
|
|
type: 'line',
|
|
label: `Weight LY (WHS ${whs})`,
|
|
data: labels.map(period =>
|
|
periodData.get(period)?.ly[whs]?.weight || 0
|
|
),
|
|
borderColor: createLighterColor(whsColors[whs]),
|
|
backgroundColor: createLighterColor(whsColors[whs]),
|
|
borderDash: [10, 10],
|
|
yAxisID: 'yLeft',
|
|
tension: 0.3,
|
|
fill: false,
|
|
order: 1
|
|
});
|
|
}
|
|
}
|
|
// Lines
|
|
if (checkLines === 'on') {
|
|
// Lines dataset for current year
|
|
datasets.push({
|
|
type: 'line',
|
|
label: `Lines TY (WHS ${whs})`,
|
|
data: labels.map(period =>
|
|
periodData.get(period)?.current[whs]?.lines || 0
|
|
),
|
|
borderColor: whsColors[whs],
|
|
backgroundColor: whsColors[whs],
|
|
// borderDash: [2, 2],
|
|
yAxisID: 'yRight',
|
|
tension: 0.3,
|
|
fill: false
|
|
});
|
|
|
|
// Lines dataset for last year
|
|
if (dataLY && dataLY.length > 0) {
|
|
datasets.push({
|
|
type: 'line',
|
|
label: `Lines LY (WHS ${whs})`,
|
|
data: labels.map(period =>
|
|
periodData.get(period)?.ly[whs]?.lines || 0
|
|
),
|
|
borderColor: createLighterColor(whsColors[whs]),
|
|
backgroundColor: createLighterColor(whsColors[whs]),
|
|
borderDash: [10, 2],
|
|
yAxisID: 'yRight',
|
|
tension: 0.3,
|
|
fill: false,
|
|
order: 1
|
|
});
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Quarterly data format handling
|
|
else if (dataFormat === "quarterly") {
|
|
// Extract unique years and quarters
|
|
const yearsSet = new Set();
|
|
const periodsSet = new Set();
|
|
|
|
data.forEach(entry => {
|
|
const [quarter, year] = entry.year.split("-");
|
|
yearsSet.add(year);
|
|
periodsSet.add(quarter);
|
|
});
|
|
|
|
const years = Array.from(yearsSet).sort();
|
|
const periods = Array.from(periodsSet).sort((a, b) =>
|
|
parseInt(a.substring(1)) - parseInt(b.substring(1))
|
|
);
|
|
|
|
// Create groupedData structure for quarters
|
|
const groupedData = new Map();
|
|
const groupedDataLY = new Map();
|
|
|
|
// Initialize data structures
|
|
years.forEach(year => {
|
|
groupedData.set(year, new Map());
|
|
periods.forEach(quarter => {
|
|
groupedData.get(year).set(quarter, new Map());
|
|
});
|
|
});
|
|
|
|
// Process current year data
|
|
data.forEach(entry => {
|
|
const [quarter, year] = entry.year.split("-");
|
|
if (!groupedData.has(year)) {
|
|
groupedData.set(year, new Map());
|
|
}
|
|
if (!groupedData.get(year).has(quarter)) {
|
|
groupedData.get(year).set(quarter, new Map());
|
|
}
|
|
if (!groupedData.get(year).get(quarter).has(entry.whs)) {
|
|
groupedData.get(year).get(quarter).set(entry.whs, {
|
|
dollars: 0, weight: 0, lines: 0
|
|
});
|
|
}
|
|
|
|
const whsData = groupedData.get(year).get(quarter).get(entry.whs);
|
|
whsData.dollars += entry.dollars;
|
|
whsData.weight += entry.weight;
|
|
whsData.lines += entry.lines;
|
|
});
|
|
|
|
// Process last year data if available
|
|
if (dataLY && dataLY.length > 0) {
|
|
// Initialize LY structure
|
|
years.forEach(year => {
|
|
groupedDataLY.set(year, new Map());
|
|
periods.forEach(quarter => {
|
|
groupedDataLY.get(year).set(quarter, new Map());
|
|
});
|
|
});
|
|
|
|
dataLY.forEach(entry => {
|
|
const [quarter, year] = entry.year.split("-");
|
|
if (!groupedDataLY.has(year)) {
|
|
groupedDataLY.set(year, new Map());
|
|
}
|
|
if (!groupedDataLY.get(year).has(quarter)) {
|
|
groupedDataLY.get(year).set(quarter, new Map());
|
|
}
|
|
if (!groupedDataLY.get(year).get(quarter).has(entry.whs)) {
|
|
groupedDataLY.get(year).get(quarter).set(entry.whs, {
|
|
dollars: 0, weight: 0, lines: 0
|
|
});
|
|
}
|
|
|
|
const whsData = groupedDataLY.get(year).get(quarter).get(entry.whs);
|
|
whsData.dollars += entry.dollars;
|
|
whsData.weight += entry.weight;
|
|
whsData.lines += entry.lines;
|
|
});
|
|
}
|
|
|
|
// Set labels to quarters
|
|
labels = periods;
|
|
|
|
// Create color map for years
|
|
const yearColors = {};
|
|
const generatedColors = generateDistinctColors(years.length);
|
|
years.forEach((year, index) => {
|
|
yearColors[year] = generatedColors[index];
|
|
});
|
|
|
|
// Create datasets
|
|
whsArray.forEach(whs => {
|
|
years.forEach(year => {
|
|
if (checkSales === 'on') {
|
|
// Current year sales
|
|
datasets.push({
|
|
type: 'line',
|
|
label: `Sales (WHS ${whs} - ${year})`,
|
|
data: periods.map(period =>
|
|
groupedData.get(year)?.get(period)?.get(whs)?.dollars || 0
|
|
),
|
|
borderColor: yearColors[year],
|
|
backgroundColor: yearColors[year],
|
|
yAxisID: 'yLeft',
|
|
tension: 0.3,
|
|
fill: false
|
|
});
|
|
|
|
// Last year sales
|
|
if (dataLY && dataLY.length > 0) {
|
|
datasets.push({
|
|
type: 'line',
|
|
label: `Sales LY (WHS ${whs} - ${year})`,
|
|
data: periods.map(period =>
|
|
groupedDataLY.get(year)?.get(period)?.get(whs)?.dollars || 0
|
|
),
|
|
borderColor: yearColors[year],
|
|
backgroundColor: yearColors[year],
|
|
borderDash: [10, 5],
|
|
yAxisID: 'yLeft',
|
|
tension: 0.3,
|
|
fill: false,
|
|
order: 1
|
|
});
|
|
}
|
|
}
|
|
|
|
if (checkWeight === 'on') {
|
|
// Current year weight
|
|
datasets.push({
|
|
type: 'line',
|
|
label: `Weight (WHS ${whs} - ${year})`,
|
|
data: periods.map(period =>
|
|
groupedData.get(year)?.get(period)?.get(whs)?.weight || 0
|
|
),
|
|
borderColor: yearColors[year],
|
|
backgroundColor: yearColors[year],
|
|
borderDash: [5, 5],
|
|
yAxisID: 'yLeft',
|
|
tension: 0.3,
|
|
fill: false
|
|
});
|
|
|
|
// Last year weight
|
|
if (dataLY && dataLY.length > 0) {
|
|
datasets.push({
|
|
type: 'line',
|
|
label: `Weight LY (WHS ${whs} - ${year})`,
|
|
data: periods.map(period =>
|
|
groupedDataLY.get(year)?.get(period)?.get(whs)?.weight || 0
|
|
),
|
|
borderColor: yearColors[year],
|
|
backgroundColor: yearColors[year],
|
|
borderDash: [10, 10],
|
|
yAxisID: 'yLeft',
|
|
tension: 0.3,
|
|
fill: false,
|
|
order: 1
|
|
});
|
|
}
|
|
}
|
|
|
|
if (checkLines === 'on') {
|
|
// Current year lines
|
|
datasets.push({
|
|
type: 'line',
|
|
label: `Lines (WHS ${whs} - ${year})`,
|
|
data: periods.map(period =>
|
|
groupedData.get(year)?.get(period)?.get(whs)?.lines || 0
|
|
),
|
|
borderColor: yearColors[year],
|
|
backgroundColor: yearColors[year],
|
|
borderDash: [2, 2],
|
|
yAxisID: 'yRight',
|
|
tension: 0.3,
|
|
fill: false
|
|
});
|
|
|
|
// Last year lines
|
|
if (dataLY && dataLY.length > 0) {
|
|
datasets.push({
|
|
type: 'line',
|
|
label: `Lines LY (WHS ${whs} - ${year})`,
|
|
data: periods.map(period =>
|
|
groupedDataLY.get(year)?.get(period)?.get(whs)?.lines || 0
|
|
),
|
|
borderColor: yearColors[year],
|
|
backgroundColor: yearColors[year],
|
|
borderDash: [10, 2],
|
|
yAxisID: 'yRight',
|
|
tension: 0.3,
|
|
fill: false,
|
|
order: 1
|
|
});
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
// Yearly or daily format handling
|
|
else {
|
|
// Group data by year/day and warehouse
|
|
const groupedData = new Map();
|
|
const groupedDataLY = new Map();
|
|
|
|
// Process current year data
|
|
data.forEach(entry => {
|
|
if (!groupedData.has(entry.year)) {
|
|
groupedData.set(entry.year, new Map());
|
|
}
|
|
if (!groupedData.get(entry.year).has(entry.whs)) {
|
|
groupedData.get(entry.year).set(entry.whs, {
|
|
dollars: 0, weight: 0, lines: 0
|
|
});
|
|
}
|
|
|
|
const whsData = groupedData.get(entry.year).get(entry.whs);
|
|
whsData.dollars += entry.dollars;
|
|
whsData.weight += entry.weight;
|
|
whsData.lines += entry.lines;
|
|
});
|
|
|
|
// Process last year data if available
|
|
if (dataLY && dataLY.length > 0) {
|
|
dataLY.forEach(entry => {
|
|
if (!groupedDataLY.has(entry.year)) {
|
|
groupedDataLY.set(entry.year, new Map());
|
|
}
|
|
if (!groupedDataLY.get(entry.year).has(entry.whs)) {
|
|
groupedDataLY.get(entry.year).set(entry.whs, {
|
|
dollars: 0, weight: 0, lines: 0
|
|
});
|
|
}
|
|
|
|
const whsData = groupedDataLY.get(entry.year).get(entry.whs);
|
|
whsData.dollars += entry.dollars;
|
|
whsData.weight += entry.weight;
|
|
whsData.lines += entry.lines;
|
|
});
|
|
}
|
|
|
|
// Set labels to sorted years or days
|
|
labels = Array.from(groupedData.keys()).sort();
|
|
|
|
// Helper function to get time key
|
|
function getTimeKey(yearLabel) {
|
|
if (yearLabel.includes("Q")) {
|
|
return yearLabel.split("-")[0] + "-";
|
|
} else if (yearLabel.match(/^\d{4}-\d{2}$/)) {
|
|
return yearLabel.split("-")[1]; // Get month part
|
|
} else if (/^\d{2}$/.test(yearLabel)) {
|
|
return yearLabel;
|
|
}
|
|
return yearLabel;
|
|
}
|
|
|
|
// Create color map
|
|
let uniqueKeys = new Set();
|
|
data.forEach(item => {
|
|
const timeKey = getTimeKey(item.year);
|
|
whsArray.forEach(whs => {
|
|
if (/^\d{2}$/.test(item.year)) {
|
|
// Daily case
|
|
uniqueKeys.add(`${whs}_Sales`);
|
|
uniqueKeys.add(`${whs}_Weight`);
|
|
uniqueKeys.add(`${whs}_Lines`);
|
|
} else {
|
|
// Yearly case
|
|
uniqueKeys.add(`${timeKey}_${whs}`);
|
|
}
|
|
});
|
|
});
|
|
uniqueKeys = [...uniqueKeys];
|
|
|
|
let colorMap = {};
|
|
let generatedColors = generateDistinctColors(uniqueKeys.length);
|
|
uniqueKeys.forEach((key, index) => {
|
|
colorMap[key] = generatedColors[index];
|
|
});
|
|
|
|
// Function to get color
|
|
function getDatasetColor(year, whs, metric, isLastYear = false) {
|
|
const timeKey = getTimeKey(year);
|
|
const key = /^\d{2}$/.test(year) ?
|
|
`${whs}_${metric}${isLastYear ? '_LY' : ''}` :
|
|
`${timeKey}_${whs}`;
|
|
return colorMap[key] || "rgba(200, 200, 200, 0.6)";
|
|
}
|
|
|
|
// Create datasets
|
|
whsArray.forEach(whs => {
|
|
// Function to create dataset with unique colors
|
|
const createDataset = (label, dataKey, stack, yAxisID, metric, isLastYear = false, dataGroup) => ({
|
|
type: 'line',
|
|
label: `${label}${isLastYear ? ' LY' : ''} (WHS ${whs} - ${metric})`,
|
|
data: labels.map(year => dataGroup.get(year)?.get(whs)?.[dataKey] || 0),
|
|
borderColor: labels.map(year => getDatasetColor(year, whs, metric, isLastYear)),
|
|
backgroundColor: labels.map(year => getDatasetColor(year, whs, metric, isLastYear)),
|
|
borderDash: isLastYear ? [10, 5] : undefined,
|
|
stack,
|
|
yAxisID,
|
|
tension: 0.3,
|
|
fill: false,
|
|
order: isLastYear ? 1 : 0
|
|
});
|
|
|
|
if (checkSales === 'on') {
|
|
// Current year data
|
|
datasets.push(createDataset("Sales", "dollars", "Sales", 'yLeft', "Sales", false, groupedData));
|
|
|
|
// Last year data for daily format
|
|
if ((dataFormat === "daily" || dataFormat === "yearly") && dataLY && dataLY.length > 0) {
|
|
datasets.push(createDataset("Sales", "dollars", "Sales_LY", 'yLeft', "Sales", true, groupedDataLY));
|
|
}
|
|
}
|
|
|
|
if (checkWeight === 'on') {
|
|
// Current year data
|
|
datasets.push(createDataset("Weight", "weight", "Weight", 'yLeft', "Weight", false, groupedData));
|
|
|
|
// Last year data for daily/yearly format
|
|
if ((dataFormat === "daily" || dataFormat === "yearly") && dataLY && dataLY.length > 0) {
|
|
datasets.push(createDataset("Weight", "weight", "Weight_LY", 'yLeft', "Weight", true, groupedDataLY));
|
|
}
|
|
}
|
|
|
|
if (checkLines === 'on') {
|
|
// Current year data
|
|
datasets.push(createDataset("Lines", "lines", "Lines", 'yRight', "Lines", false, groupedData));
|
|
|
|
// Last year data for daily/yearly format
|
|
if ((dataFormat === "daily" || dataFormat === "yearly") && dataLY && dataLY.length > 0) {
|
|
datasets.push(createDataset("Lines", "lines", "Lines_LY", 'yRight', "Lines", true, groupedDataLY));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Generate Chart
|
|
window.execSummaryChart = new Chart(
|
|
document.getElementById('acquisitions'), {
|
|
type: 'line',
|
|
data: {
|
|
labels,
|
|
datasets
|
|
},
|
|
options: {
|
|
interaction: {
|
|
intersect: false,
|
|
mode: 'index'
|
|
},
|
|
responsive: true,
|
|
elements: {
|
|
point: {
|
|
radius: 5,
|
|
hoverRadius: 8
|
|
}
|
|
},
|
|
scales: {
|
|
yLeft: {
|
|
type: 'linear',
|
|
position: 'left',
|
|
title: {
|
|
display: true,
|
|
text: 'Sales & Weight'
|
|
},
|
|
beginAtZero: true
|
|
},
|
|
yRight: {
|
|
type: 'linear',
|
|
position: 'right',
|
|
title: {
|
|
display: true,
|
|
text: 'Lines'
|
|
},
|
|
grid: {
|
|
drawOnChartArea: false
|
|
},
|
|
beginAtZero: true
|
|
},
|
|
x: {
|
|
title: {
|
|
display: true,
|
|
text: dataFormat === "quarterly" ? 'Quarter' :
|
|
dataFormat === "monthly" ? 'Month' :
|
|
dataFormat === "daily" ? 'Day' : 'Year'
|
|
}
|
|
}
|
|
},
|
|
plugins: {
|
|
tooltip: {
|
|
callbacks: {
|
|
title: function(tooltipItems) {
|
|
return tooltipItems[0].label;
|
|
// return tooltipItems[0].raw.label;
|
|
}
|
|
}
|
|
},
|
|
legend: {
|
|
position: 'top',
|
|
labels: {
|
|
usePointStyle: true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
);
|
|
}
|
|
// End Success
|
|
},
|
|
error: function(xhr, status, error) {
|
|
console.error("Error:", error);
|
|
// Display error message to user
|
|
Swal.fire({
|
|
icon: 'error',
|
|
title: 'Ajax Error',
|
|
text: 'Failed to load chart data. Please try again.',
|
|
confirmButtonText: 'OK'
|
|
});
|
|
}
|
|
});
|
|
// End Ajax
|
|
});
|
|
|
|
//// Trigger Click on init
|
|
$("#updateCharts").trigger("click");
|
|
|
|
});
|
|
</script>
|
|
@endsection
|