diff --git a/dbbackup/auth_chart_order.sql b/dbbackup/auth_chart_order.sql new file mode 100644 index 0000000..077565d --- /dev/null +++ b/dbbackup/auth_chart_order.sql @@ -0,0 +1,32 @@ +/* + Navicat Premium Dump SQL + + Source Server : local + Source Server Type : MySQL + Source Server Version : 100432 (10.4.32-MariaDB) + Source Host : localhost:3306 + Source Schema : reportifynew + + Target Server Type : MySQL + Target Server Version : 100432 (10.4.32-MariaDB) + File Encoding : 65001 + + Date: 25/09/2024 01:08:34 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for auth_chart_order +-- ---------------------------- +DROP TABLE IF EXISTS `chart_order`; +CREATE TABLE `chart_order` ( + `id` int NOT NULL, + `user_id` int NULL DEFAULT NULL, + `order` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `insert_date` datetime NULL DEFAULT current_timestamp ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/public/userarea/assets/pages/dashboard.js b/public/userarea/assets/pages/dashboard.js index 540b2dc..14427d5 100644 --- a/public/userarea/assets/pages/dashboard.js +++ b/public/userarea/assets/pages/dashboard.js @@ -11,7 +11,9 @@ //line-chart -var ctx = document.getElementById('lineChart').getContext('2d'); +if( $('#lineChart').length > 0 ){ + var ctx = document.getElementById('lineChart').getContext('2d'); +} gradientStroke1 = ctx.createLinearGradient(0, 0, 0, 300); gradientStroke1.addColorStop(0, '#008cff'); diff --git a/public/userarea/statkpi/chartorder.php b/public/userarea/statkpi/chartorder.php new file mode 100644 index 0000000..6d96484 --- /dev/null +++ b/public/userarea/statkpi/chartorder.php @@ -0,0 +1,38 @@ +connect_error) { + die("Connection failed: " . $conn->connect_error); +} + +// two methods save and load data +if ($_POST['method'] == 'save') { + $order = $_POST['order']; + $user_id = $iduserlogin; + // check if user already have order + $sql = "SELECT * FROM chart_order WHERE user_id = $user_id"; + $result = $conn->query($sql); + $order = implode(',', $order); + if ($result->num_rows > 0) { + $sql = "UPDATE chart_order SET `order` = '$order' WHERE user_id = $user_id"; + } else { + $sql = "INSERT INTO chart_order (user_id, `order`) VALUES ($user_id, '$order')"; + } + $conn->query($sql); + echo 'Data saved'; +} else if($_POST['method'] == 'load') { + $sql = "SELECT `order` FROM chart_order WHERE user_id = $iduserlogin ORDER BY insert_date DESC LIMIT 1"; + $result = $conn->query($sql); + if($result->num_rows == 0) { + echo json_encode([]); + return; + }else{ + $row = $result->fetch_assoc(); + $order_list = explode(',', $row['order']); + echo json_encode($order_list); + } +} \ No newline at end of file diff --git a/public/userarea/statkpi/parsedatachart.php b/public/userarea/statkpi/parsedatachart.php index 9f9f30d..61a7c61 100644 --- a/public/userarea/statkpi/parsedatachart.php +++ b/public/userarea/statkpi/parsedatachart.php @@ -1,11 +1,97 @@ query($totalProductsQuery); $totalProducts = $totalProductsResult->fetch_assoc()['totalProducts']; @@ -138,13 +256,28 @@ while ($row = $worstSuppliersResult->fetch_assoc()) { ]; } +$suPfilters=''; // Statistic for products by suppliers + +if(!empty($supplierFilter)){ + $suPfilters .= " AND p.namesupplier IN ($supplierFilter)"; +} +if(!empty($refNumberFilter)){ + $suPfilters .= " AND p.products_refnumber IN ($refNumberFilter)"; +} +if(!empty($productsSeasonFilter)){ + $suPfilters .= " AND p.products_season IN ($productsSeasonFilter)"; +} +if(!empty($ageRangeFilter)){ + $suPfilters .= " AND p.agerange IN ($ageRangeFilter)"; +} $productBySupplierQuery = " SELECT p.namesupplier AS supplier, COUNT(p.idproducts) AS totalProducts FROM products p - WHERE p.namesupplier IS NOT NULL + WHERE p.namesupplier IS NOT NULL $suPfilters GROUP BY p.namesupplier - ORDER BY totalProducts DESC"; + ORDER BY totalProducts DESC"; + $productBySupplierResult = $conn->query($productBySupplierQuery); $productBySupplier = []; while ($row = $productBySupplierResult->fetch_assoc()) { @@ -153,6 +286,96 @@ while ($row = $productBySupplierResult->fetch_assoc()) { 'totalProducts' => $row['totalProducts'] ]; } +// refNumbers +$refNumbersQuery = " + SELECT p.products_refnumber AS refNumber + FROM products p + WHERE p.products_refnumber IS NOT NULL + GROUP BY p.products_refnumber +"; +$refNumbersResult = $conn->query($refNumbersQuery); +$refNumbers = []; +while ($row = $refNumbersResult->fetch_assoc()) { + $refNumbers[] = [ + 'refNumber' => $row['refNumber'] + ]; +} +// productsSeason +$productsSeasonQuery = " + SELECT p.products_season AS season + FROM products p + WHERE p.products_season IS NOT NULL + GROUP BY p.products_season +"; +$productsSeasonResult = $conn->query($productsSeasonQuery); +$productsSeasons = []; +while ($row = $productsSeasonResult->fetch_assoc()) { + $productsSeasons[] = [ + "season" => $row['season'] + ]; +} + +// ageRanges +$ageRangeQuery = " + SELECT p.agerange AS ageRange + FROM products p + WHERE p.agerange IS NOT NULL + GROUP BY p.agerange +"; + +$ageRangeResult = $conn->query($ageRangeQuery); +$ageRange = []; +while ($row = $ageRangeResult->fetch_assoc()) { + $ageRange[] = [ + "ageRange" => $row['ageRange'] + ]; +} + +// labNames +$labNameQuery = " + SELECT r.reports_LabName AS LabName + FROM reports r + WHERE r.reports_LabName IS NOT NULL + GROUP BY r.reports_LabName +"; +$labNameResult = $conn->query($labNameQuery); +$labName = []; +while ($row = $labNameResult->fetch_assoc()) { + $labName[] = [ + "LabName" => $row['LabName'] + ]; +} + +// tesTypes +$tesTypeQuery = " + SELECT r.reports_testype AS tesType + FROM reports r + WHERE r.reports_testype IS NOT NULL + GROUP BY r.reports_testype +"; +$tesTypeResult = $conn->query($tesTypeQuery); +$tesType = []; +while ($row = $tesTypeResult->fetch_assoc()) { + $tesType[] = [ + "tesType" => $row['tesType'] + ]; +} + +// numberLabs +$numberLabsQuery = " + SELECT r.reportsNumberLab as reportNumber + FROM reports r + WHERE r.reportsNumberLab IS NOT NULL + GROUP BY r.reportsNumberLab +"; +$numberLabsResult = $conn->query($numberLabsQuery); +$numberLabs = []; +while ($row = $numberLabsResult->fetch_assoc()) { + $numberLabs[] = [ + "reportNumber" => $row['reportNumber'] + ]; +} + // New Query: Distribution of analyses (for pie chart) $analysisDistributionQuery = " @@ -192,6 +415,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { 'topFailingAnalysis' => $topFailingAnalysis, 'worstSuppliers' => $worstSuppliers, 'productBySupplier' => $productBySupplier, + 'refNumbers' => $refNumbers, + 'productsSeasons' => $productsSeasons, + 'ageRange' => $ageRange, + 'labName' => $labName, + 'tesType' => $tesType, + 'numberLabs' => $numberLabs, 'analysisDistribution' => $analysisDistribution // Distribuzione delle analisi per il grafico a torta ]); exit; // Ferma l'esecuzione del resto dello script dopo aver risposto all'AJAX diff --git a/public/userarea/statkpi/statkpi.php b/public/userarea/statkpi/statkpi.php index 872e130..84620f4 100644 --- a/public/userarea/statkpi/statkpi.php +++ b/public/userarea/statkpi/statkpi.php @@ -32,6 +32,21 @@ include('parsedatachart.php'); @@ -208,6 +233,40 @@ include('parsedatachart.php'); + + + + + @@ -223,9 +282,9 @@ include('parsedatachart.php'); -
+
-
+
@@ -242,6 +301,140 @@ include('parsedatachart.php');
+ + + +
+ $refNumbers, + // 'productsSeasons' => $productsSeasons, + // 'ageRange' => $ageRange, + // 'labName' => $labName, + // 'tesType' => $tesType, + // 'numberLabs' => $numberLabs, + + ?> +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ + +
@@ -262,8 +455,7 @@ include('parsedatachart.php');
- @@ -272,9 +464,9 @@ include('parsedatachart.php');

- -
-
-
-
+
+ +
+
+
+
- -
-
-
-
-
Products
-

0

-
-
-
-
-
-
-
Reports
-

0

-
-
-
-
-
-
-
Failed Reports
-
-

0

- (0%) + +
+
+
+
+
Products
+

0

-
-
-
-
-
Total Tests
-

0

+
+
+
+
Reports
+

0

+
-
-
-
-
-
Failed Tests
-
-

0

- (0%) +
+
+
+
Failed Reports
+
+

0

+ (0%) +
+
+
+
+
+
+
+
Total Tests
+

0

+
+
+
+
+
+
+
Failed Tests
+
+

0

+ (0%) +
@@ -344,64 +539,63 @@ include('parsedatachart.php');
-
- + -
-
-
-
-
Reports Overview
-
+
+
+
+
+
Reports Overview
+
+
+
+
+
+
+
+
Worst Analysis
+
+
-
-
-
-
Worst Analysis
-
+ +
+
+
+
+
Worst Suppliers by Failed Report Percentage
+
+
-
-
-
-
-
-
Worst Suppliers by Failed Report Percentage
-
+
+
+
+
+
Number of Products by Supplier
+
+
-
-
-
-
-
-
Number of Products by Supplier
-
+
+
+
+
+
Analysis Distribution
+
+
+ + +
- -
-
-
-
-
Analysis Distribution
-
-
-
-
-
- - - -
@@ -415,75 +609,73 @@ include('parsedatachart.php');
+ @@ -495,6 +687,12 @@ include('parsedatachart.php'); var startDate = $('#startDate').val(); var endDate = $('#endDate').val(); var supplier = $('#supplierFilter').val(); + var productsRefnumber = $('#productsRefnumber').val(); + var productsSeason = $('#productsSeason').val(); + var ageRange = $('#ageRange').val(); + var reportsLabName = $('#reportsLabName').val(); + var reportsTestType = $('#reportsTestType').val(); + var reportsNumberLab = $('#reportsNumberLab').val(); $.ajax({ url: 'parsedatachart.php', @@ -502,10 +700,22 @@ include('parsedatachart.php'); data: { startDate: startDate, endDate: endDate, - supplier: supplier + supplier: supplier, + productsRefnumber: productsRefnumber, + productsSeason: productsSeason, + ageRange: ageRange, + reportsLabName: reportsLabName, + reportsTestType: reportsTestType, + reportsNumberLab: reportsNumberLab }, success: function(response) { - var data = JSON.parse(response); + if (!response) { + alert('No data found.'); + data = {}; + return; + }else{ + var data = JSON.parse(response); + } // Aggiorna le cards con i nuovi dati $('#totalProducts').text(data.totalProducts); @@ -530,16 +740,326 @@ include('parsedatachart.php'); return parseInt(item.totalTests, 10); // Converte il conteggio dei test in numeri interi }); - // Aggiorna il grafico a barre verticali - analysisBarChart.updateOptions({ - xaxis: { - categories: analysisLabels // Aggiorna le etichette - } - }); - analysisBarChart.updateSeries([{ - data: analysisCounts // Aggiorna i dati del grafico - }]); } + + // remove pie chart and create a new one + $('#reportPieChart').html(''); + + var intFailReports = parseInt(data.failReportsPie); + var intPassReports = parseInt(data.passReportsPie); + var intOtherReports = parseInt(data.otherReportsPie); + + // Data for pie chart (Reports: Fail, Pass, Others) + var options = { + series: [intFailReports, intPassReports, intOtherReports], + chart: { + width: '100%', // Mantieni la larghezza al 100% all'interno della colonna Bootstrap + type: 'pie', + }, + labels: ['Fail', 'Pass', 'Others'], + colors: ['#FF4D4D', '#28A745', '#FFA500'], // Red for Fail, Green for Pass, Orange for Others + responsive: [{ + breakpoint: 480, + options: { + chart: { + width: 250 // Riduci la larghezza sui dispositivi mobili + }, + legend: { + position: 'bottom' + } + } + }], + legend: { + position: 'bottom', // Posiziona la legenda sotto il grafico + offsetY: 0, + height: 50, // Altezza della legenda + } + }; + + var chart = new ApexCharts(document.querySelector("#reportPieChart"), options); + chart.render(); + + // remove bar chart and create a new one + $('#worsttenanalysis').html(''); + + // Data for the bar chart + var analysisNames = data.topFailingAnalysis.map(function(item) { + return item.name; + }); + var failCounts = data.topFailingAnalysis.map(function(item) { + return parseInt(item.failCount, 10); + }); + + var options = { + series: [{ + data: failCounts + }], + chart: { + type: 'bar', + height: 350 + }, + plotOptions: { + bar: { + horizontal: true, + dataLabels: { + position: 'center' // Posiziona i nomi delle analisi al centro delle barre + } + } + }, + dataLabels: { + enabled: true, + style: { + colors: ['#fff'], // Colore del testo all'interno delle barre (bianco) + fontSize: '12px' + }, + formatter: function(val, opt) { + return analysisNames[opt.dataPointIndex]; // Mostra il nome dell'analisi dentro la barra + } + }, + xaxis: { + categories: failCounts, // Visualizza solo i numeri sull'asse X + title: { + text: 'Number of Failures' + } + }, + yaxis: { + labels: { + show: false // Nascondiamo le etichette dell'asse Y + }, + title: { + text: 'Analysis' + } + }, + colors: ['#FF4D4D'], // Rosso per i Fail + responsive: [{ + breakpoint: 480, + options: { + chart: { + height: 300 + }, + xaxis: { + labels: { + show: true + } + } + } + }], + title: { + text: 'Top 10 Analyses with the Most Failures', + align: 'center' + } + }; + + var chart = new ApexCharts(document.querySelector("#worsttenanalysis"), options); + chart.render(); + + // remove bar chart and create a new one + $('#worstSuppliersChart').html(''); + + // Data for the bar chart of worst suppliers + var supplierNames = data.worstSuppliers.map(function(item) { + return item.supplier; + }); + var failPercentages = data.worstSuppliers.map(function(item) { + return parseFloat(item.failPercentage); + }); + var totalReportsForSupplier = data.worstSuppliers.map(function(item) { + return parseInt(item.totalReports, 10); + }); + var failedReportsForSupplier = data.worstSuppliers.map(function(item) { + return parseInt(item.failedReports, 10); + }); + + var options = { + series: [{ + data: failPercentages + }], + chart: { + type: 'bar', + height: 400 + }, + plotOptions: { + bar: { + horizontal: true, + dataLabels: { + position: 'center' // Etichette al centro delle barre + } + } + }, + dataLabels: { + enabled: true, + style: { + colors: ['#fff'], // Colore del testo all'interno delle barre + fontSize: '12px' + }, + formatter: function(val, opt) { + // Aggiungi nome del fornitore, percentuale, numero di fail e numero totale di report + var totalReports = totalReportsForSupplier[opt.dataPointIndex]; // Numero totale di report + var failedReports = failedReportsForSupplier[opt.dataPointIndex]; // Numero di report falliti + return supplierNames[opt.dataPointIndex] + ' (' + val.toFixed(2) + '%) (Fail: ' + failedReports + ' - Total: ' + totalReports + ')'; + } + }, + xaxis: { + categories: supplierNames, // Visualizza i nomi dei fornitori + title: { + text: 'Failure Percentage (%)' + } + }, + yaxis: { + labels: { + show: false // Nascondiamo le etichette dell'asse Y + }, + title: { + text: 'Suppliers' + } + }, + colors: ['#3368FF'], // Colore blu chiaro per le barre + title: { + text: 'Top 10 Suppliers with the Highest Failed Reports Percentage', + align: 'center' + } + }; + + var chart = new ApexCharts(document.querySelector("#worstSuppliersChart"), options); + chart.render(); + + // remove bar chart and create a new one + $('#productBySupplierChart').html(''); + + // Prepara i dati per il grafico + var supplierNames = data.productBySupplier.map(function(item) { + return item.supplier; + }); + var totalProducts = data.productBySupplier.map(function(item) { + return parseInt(item.totalProducts, 10); + }); + + var options = { + series: [{ + name: 'Total Products', + data: totalProducts + }], + chart: { + type: 'bar', + height: 400 + }, + plotOptions: { + bar: { + horizontal: false, // Imposta il grafico a barre verticali + columnWidth: '50%', + dataLabels: { + position: 'top', // Etichette nella parte superiore delle barre + } + } + }, + dataLabels: { + enabled: true, + offsetY: -20, + style: { + fontSize: '12px', + colors: ['#000'] + } + }, + xaxis: { + categories: supplierNames, + title: { + text: 'Suppliers' + } + }, + yaxis: { + title: { + text: 'Number of Products' + } + }, + colors: ['#3368FF'], + title: { + text: 'Number of Products by Supplier', + align: 'center' + } + }; + + var chart = new ApexCharts(document.querySelector("#productBySupplierChart"), options); + chart.render(); + + // remove bar chart and create a new one + $('#analysisDistributionChart').html(''); + + var analysisNames = data.analysisDistribution.map(function(item) { + return item.analysisName; + }); + var totalTests = data.analysisDistribution.map(function(item) { + return parseInt(item.totalTests, 10); + }); + // Define the horizontal bar chart for analysis distribution + var analysisBarChartOptions = { + series: [{ + name: 'Total Tests', + data: totalTests // Initially empty, will be filled via AJAX + }], + chart: { + type: 'bar', + height: 500 + }, + plotOptions: { + bar: { + horizontal: true, + columnWidth: '100%', + endingShape: 'rounded' + } + }, + dataLabels: { + enabled: true + }, + stroke: { + show: true, + width: 2, + colors: ['transparent'] + }, + xaxis: { + categories: analysisNames, + title: { + text: 'Number of Tests' + } + }, + yaxis: { + labels: { + show: true, + maxWidth: 700, // Increased max width for labels + style: { + fontSize: '12px', + colors: ['#000'] + } + }, + title: { + text: 'Analysis Name' + } + }, + fill: { + opacity: 1, + colors: ['#004d00'] + }, + tooltip: { + y: { + formatter: function(val) { + return val + " tests"; + } + } + }, + title: { + text: 'Analysis Distribution', + align: 'center' + }, + grid: { + padding: { + left: 10 // Increased left padding for more label space + } + } + }; + + // Create the initial chart + var analysisBarChart = new ApexCharts(document.querySelector("#analysisDistributionChart"), analysisBarChartOptions); + analysisBarChart.render(); + }, error: function() { alert('Error retrieving data.'); @@ -548,7 +1068,7 @@ include('parsedatachart.php'); } // Eventi per applicare i filtri - $('#startDate, #endDate, #supplierFilter').on('change', function() { + $('#startDate, #endDate, #supplierFilter, #productsRefnumber, #productsSeason, #ageRange, #reportsLabName, #reportsTestType, #reportsNumberLab').on('change', function() { updateData(); }); @@ -557,8 +1077,14 @@ include('parsedatachart.php'); $('#startDate').val(''); $('#endDate').val(''); $('#supplierFilter').val('').trigger('change'); + $('#productsRefnumber').val('').trigger('change'); + $('#productsSeason').val('').trigger('change'); + $('#ageRange').val('').trigger('change'); + $('#reportsLabName').val('').trigger('change'); + $('#reportsTestType').val('').trigger('change'); + $('#reportsNumberLab').val('').trigger('change'); updateData(); - $('#activeFilters').hide(); + // $('#activeFilters').hide(); }); // Chiamata iniziale per caricare i dati alla prima visualizzazione della pagina @@ -577,220 +1103,16 @@ include('parsedatachart.php'); document.getElementById('failedTestsPercent').innerText = "(%)"; - - - - - - + // for multiple select + $(document).ready(function() { + $('.select2').select2({ + placeholder: "Select options", + allowClear: true, + width: '100%' + }); + }); +