M3 Integration fixes
This commit is contained in:
@@ -0,0 +1,992 @@
|
||||
@extends('m3::liberty-theme.layout.main')
|
||||
@section('title')
|
||||
Exec Summary - Line Chart
|
||||
@endsection
|
||||
|
||||
{{-- Flat Picker CSS --}}
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/plugins/monthSelect/style.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" role="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="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
|
||||
|
||||
<!-- Flatpickr JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
|
||||
<!-- Month Select Plugin -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/plugins/monthSelect/index.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
|
||||
Reference in New Issue
Block a user