Files

519 lines
26 KiB
PHP
Executable File

@extends('m3::liberty-theme.layout.main')
@section('title')
Exec Summary
@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</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';
// $('#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';
}
});
//// 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");
}
// Check From and To dates
const fromDate = $("#REPORTDATE").val();
const toDate = $("#TOREPORTDATE").val();
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;
//// Ran Ajax
$.ajax({
url: "{{ route('m3.execsummary') }}",
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;
reportPeriod = response.reportPeriod;
// Build Data
const data = dataResult.map(item => ({
year: item.YEARS,
dollars: parseInt(item.DOLLARS),
weight: parseInt(item.WEIGHT),
lines: parseInt(item.LINES),
whs: parseInt(item.WHS)
}));
// Group data by year and WHS
const groupedData = new Map();
const whsSet = new Set();
const yearsSet = new Set();
data.forEach(entry => {
const { year, whs,dollars,weight,lines} = entry;
if (!groupedData.has(year)) {
groupedData.set(year, new Map());
yearsSet.add(year);
}
if (!groupedData.get(year).has(whs)) {groupedData.get(year).set(whs, {dollars: 0,weight: 0,lines: 0});}
const whsData = groupedData.get(year).get(whs);
whsData.dollars += dollars;
whsData.weight += weight;
whsData.lines += lines;
whsSet.add(whs);
});
// Convert Sets to Arrays
const labels = [...groupedData.keys()].sort();
const whsArray = Array.from(whsSet);
const yearsArray = Array.from(yearsSet);
//##### MAK
// Function to extract the time period (month or quarter) regardless of year
function getTimePeriod(yearLabel) {
if (yearLabel.includes("Q")) {
// Quarterly format: "Q1-24" -> Return "Q1"
return yearLabel.split("-")[0];
} else if (yearLabel.match(/^\d{2}-\d{4}$/)) {
// Monthly format: "01-2024" -> Check if odd or even
const month = parseInt(yearLabel.split("-")[0]);
return month % 2 === 0 ? "even" : "odd";
} else if (/^\d{2}$/.test(yearLabel)) {
// Daily format: "01" -> Return "day"
return "day";
}
return yearLabel; // Default case
}
// Function to check if a label is a daily format
function isDailyFormat(yearLabel) {
return /^\d{2}$/.test(yearLabel);
}
// Define base hues for metrics
const HUE_BLUE = 195; // Sales → Blue
const HUE_GREEN = 140; // Weight → Green
const HUE_GREY = 220; // Lines → Grey (desaturated)
// Create color maps - first level for period type, second level for WHS
let salesColorMap = {};
let weightColorMap = {};
let linesColorMap = {};
// Define period types
const periodTypes = ["Q1", "Q2", "Q3", "Q4", "odd", "even", "day"];
// Generate colors for each period type
periodTypes.forEach((period, periodIndex) => {
// Calculate hue variations based on period type
let salesHue = HUE_BLUE;
let weightHue = HUE_GREEN;
// Adjust hue for quarters only
if (period.startsWith("Q")) {
const quarterNum = parseInt(period.substring(1));
salesHue = HUE_BLUE + (quarterNum - 1) * 15;
weightHue = HUE_GREEN + (quarterNum - 1) * 15;
}
// For monthly periods (odd/even), use slight variations
if (period === "odd") {
salesHue = HUE_BLUE + 5;
weightHue = HUE_GREEN + 5;
} else if (period === "even") {
salesHue = HUE_BLUE + 20;
weightHue = HUE_GREEN + 20;
}
// Create color maps for each WHS within this period
salesColorMap[period] = {};
weightColorMap[period] = {};
linesColorMap[period] = {};
// Generate different shades for each WHS - darker to lighter
whsArray.forEach((whs, whsIndex) => {
// Vary lightness by WHS - start darker (25%) and get lighter
// Reverse the WHS index calculation to go from dark to light
const reversedIndex = whsArray.length - 1 - whsIndex;
const lightness = 45 + (reversedIndex % 8) * 6;
// Assign colors for each metric
salesColorMap[period][whs] = `hsl(${salesHue}, 85%, ${lightness}%)`;
weightColorMap[period][whs] = `hsl(${weightHue}, 85%, ${lightness}%)`;
linesColorMap[period][whs] = `hsl(${HUE_GREY}, 10%, ${lightness}%)`; // Pure grey for Lines
});
});
// Function to get the appropriate color based on time period, metric, and WHS
function getDatasetColor(yearLabel, metric, whs) {
const period = getTimePeriod(yearLabel);
if (metric === "Sales") {
return salesColorMap[period]?.[whs] || `hsl(${HUE_BLUE}, 85%, 50%)`;
} else if (metric === "Weight") {
return weightColorMap[period]?.[whs] || `hsl(${HUE_GREEN}, 85%, 50%)`;
} else if (metric === "Lines") {
return linesColorMap[period]?.[whs] || `hsl(${HUE_GREY}, 0%, 50%)`;
}
return "rgba(200, 200, 200, 0.6)"; // Default fallback
}
// Extract datasets
let datasets = [];
whsArray.forEach(whs => {
// Function to create dataset with colors based on time period, metric, and WHS
const createDataset = (label, dataKey, stack, yAxisID, metric) => ({
label: `${label} (WHS ${whs} - ${metric})`,
data: labels.map(year => groupedData.get(year)?.get(whs)?.[dataKey] || 0),
backgroundColor: labels.map(year => getDatasetColor(year, metric, whs)),
stack,
yAxisID,
borderWidth: 2
});
if (checkSales === 'on') {
datasets.push(createDataset("Sales", "dollars", "Sales", 'yLeft', "Sales"));
}
if (checkWeight === 'on') {
datasets.push(createDataset("Weight", "weight", "Weight", 'yLeft', "Weight"));
}
if (checkLines === 'on') {
datasets.push(createDataset("Lines", "lines", "Lines", 'yRight', "Lines"));
}
});
//###### END MAK
// Generate Chart
window.execSummaryChart = new Chart(
document.getElementById('acquisitions'), {
type: 'bar',
data: {
labels,
datasets
},
options: {
interaction: {
intersect: false
},
responsive: true,
scales: {
yLeft: {
type: 'linear',
position: 'left',
title: {
display: true,
text: 'Sales & Weight'
},
stacked: true
},
yRight: {
type: 'linear',
position: 'right',
title: {
display: true,
text: 'Lines'
},
grid: {
drawOnChartArea: false
},
stacked: true
},
x: {
stacked: true
},
}
}
}
);
}
},
error: function(xhr, status, error) {
console.error("Error:", error);
// Handle errors
}
});
// End Ajax
});
//// Trigger Click on init
$("#updateCharts").trigger("click");
});
</script>
@endsection