Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 67 additions & 3 deletions app/Http/Controllers/ReportController.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,14 @@ public function trainings($filterArea = false)
// Get stats
$cardStats = $this->getCardStats($filterArea);
$totalRequests = $this->getDailyRequestsStats($filterArea);
[$newRequests, $completedRequests, $closedRequests, $passFailRequests] = $this->getBiAnnualRequestsStats($filterArea);
[$newRequests, $completedRequests, $closedRequests, $passFailRequests, $sessionsPerRating] = $this->getBiAnnualRequestsStats($filterArea);
$queues = $this->getQueueStats($filterArea);

// Send it to the view
($filterArea) ? $filterName = Area::find($filterArea)->name : $filterName = 'All Areas';
$areas = Area::all();

return view('reports.trainings', compact('filterName', 'areas', 'cardStats', 'totalRequests', 'newRequests', 'completedRequests', 'closedRequests', 'passFailRequests', 'queues'));
return view('reports.trainings', compact('filterName', 'areas', 'cardStats', 'totalRequests', 'newRequests', 'completedRequests', 'closedRequests', 'passFailRequests', 'queues', 'sessionsPerRating'));
}

/**
Expand Down Expand Up @@ -271,6 +271,7 @@ protected function getBiAnnualRequestsStats($areaFilter)
$completedRequests = [];
$closedRequests = [];
$passFailRequests = [];
$sessionsPerRating = [];

if ($areaFilter) {
foreach (Rating::all() as $rating) {
Expand All @@ -279,6 +280,7 @@ protected function getBiAnnualRequestsStats($areaFilter)
$closedRequests[$rating->name] = [0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0];
$passFailRequests['Passed'] = [0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0];
$passFailRequests['Failed'] = [0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0];
$sessionsPerRating[$rating->name] = [0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0];

// New requests
$query = DB::table('trainings')
Expand Down Expand Up @@ -326,6 +328,37 @@ protected function getBiAnnualRequestsStats($areaFilter)
foreach ($query as $entry) {
$closedRequests[$rating->name][$monthTranslator[$entry->month]] = $entry->count;
}

// Training reports per rating
$subquery = DB::table('trainings as t')
->join('training_reports as tr', 't.id', '=', 'tr.training_id')
->join('rating_training as rt', 't.id', '=', 'rt.training_id')
->select(
't.id as training_id',
DB::raw('MONTH(tr.created_at) as month'),
'rt.rating_id',
DB::raw('COUNT(tr.id) as report_count')
)
->where('tr.created_at', '>=', now()->subMonths(6)->startOfMonth())
->where('t.area_id', $areaFilter)
->where('rt.rating_id', $rating->id)
->groupBy('t.id', DB::raw('MONTH(tr.created_at)'), 'rt.rating_id');

$query = DB::table(DB::raw("({$subquery->toSql()}) as training_reports_per_month"))
->mergeBindings($subquery)
->select(
'month',
'rating_id',
DB::raw('ROUND(AVG(report_count), 2) as avg_reports_per_training')
)
->groupBy('month', 'rating_id')
->orderBy('month')
->orderBy('rating_id')
->get();

foreach ($query as $entry) {
$sessionsPerRating[$rating->name][$monthTranslator[$entry->month]] = $entry->avg_reports_per_training;
}
}

// Passed trainings except S1
Expand Down Expand Up @@ -381,6 +414,7 @@ protected function getBiAnnualRequestsStats($areaFilter)
$closedRequests[$rating->name] = [0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0];
$passFailRequests['Passed'] = [0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0];
$passFailRequests['Failed'] = [0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0];
$sessionsPerRating[$rating->name] = [0 => 0, 1 => 0, 2 => 0, 3 => 0, 4 => 0, 5 => 0, 6 => 0];

// New requests
$query = DB::table('trainings')
Expand Down Expand Up @@ -425,6 +459,36 @@ protected function getBiAnnualRequestsStats($areaFilter)
foreach ($query as $entry) {
$closedRequests[$rating->name][$monthTranslator[$entry->month]] = $entry->count;
}

// Training reports per rating
$subquery = DB::table('trainings as t')
->join('training_reports as tr', 't.id', '=', 'tr.training_id')
->join('rating_training as rt', 't.id', '=', 'rt.training_id')
->select(
't.id as training_id',
DB::raw('MONTH(tr.created_at) as month'),
'rt.rating_id',
DB::raw('COUNT(tr.id) as report_count')
)
->where('tr.created_at', '>=', now()->subMonths(6)->startOfMonth())
->where('rt.rating_id', $rating->id)
->groupBy('t.id', DB::raw('MONTH(tr.created_at)'), 'rt.rating_id');

$query = DB::table(DB::raw("({$subquery->toSql()}) as training_reports_per_month"))
->mergeBindings($subquery)
->select(
'month',
'rating_id',
DB::raw('ROUND(AVG(report_count), 2) as avg_reports_per_training')
)
->groupBy('month', 'rating_id')
->orderBy('month')
->orderBy('rating_id')
->get();

foreach ($query as $entry) {
$sessionsPerRating[$rating->name][$monthTranslator[$entry->month]] = $entry->avg_reports_per_training;
}
}

// Passed trainings
Expand Down Expand Up @@ -473,7 +537,7 @@ protected function getBiAnnualRequestsStats($areaFilter)

}

return [$newRequests, $completedRequests, $closedRequests, $passFailRequests];
return [$newRequests, $completedRequests, $closedRequests, $passFailRequests, $sessionsPerRating];
}

/**
Expand Down
99 changes: 86 additions & 13 deletions resources/views/reports/trainings.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@if(\Auth::user()->isModeratorOrAbove($area))
<a class="btn btn-sm {{ $filterName == $area->name ? 'btn-primary' : 'btn-outline-primary' }}" href="{{ route('reports.training.area', $area->id) }}">{{ $area->name }}</a>
@endif
@endforeach
@endforeach
</div>
@endsection
@section('content')
Expand Down Expand Up @@ -114,7 +114,7 @@
<div class="card-header bg-primary py-3 d-flex flex-row align-items-center justify-content-between">
<h6 class="m-0 fw-bold text-white">
Training requests last 12 months
</h6>
</h6>
</div>
<div class="card-body">
<canvas id="trainingChart"></canvas>
Expand All @@ -131,7 +131,7 @@
<div class="card-header bg-primary py-3 d-flex flex-row align-items-center justify-content-between">
<h6 class="m-0 fw-bold text-white">
New requests last 6 months
</h6>
</h6>
</div>
<div class="card-body">
<canvas id="newTrainingRequests"></canvas>
Expand All @@ -144,7 +144,7 @@
<div class="card-header bg-primary py-3 d-flex flex-row align-items-center justify-content-between">
<h6 class="m-0 fw-bold text-white">
Completed requests last 6 months
</h6>
</h6>
</div>
<div class="card-body">
<canvas id="completedTrainingRequests"></canvas>
Expand All @@ -157,7 +157,7 @@
<div class="card-header bg-primary py-3 d-flex flex-row align-items-center justify-content-between">
<h6 class="m-0 fw-bold text-white">
Closed requests last 6 months
</h6>
</h6>
</div>
<div class="card-body">
<canvas id="closedTrainingRequests"></canvas>
Expand All @@ -170,20 +170,33 @@
<div class="card-header bg-primary py-3 d-flex flex-row align-items-center justify-content-between">
<h6 class="m-0 fw-bold text-white">
Passed and failed exams last 6 months
</h6>
</h6>
</div>
<div class="card-body">
<canvas id="TrainingPassFailRate"></canvas>
</div>
</div>
</div>

<div class="col-xl-4 col-md-12 mb-12 d-none d-xl-block d-lg-block d-md-block">
<div class="card shadow mb-4">
<div class="card-header bg-primary py-3 d-flex flex-row align-items-center justify-content-between">
<h6 class="m-0 fw-bold text-white">
Training sessions per rating last 6 months
</h6>
</div>
<div class="card-body">
<canvas id="sessionsPerRating"></canvas>
</div>
</div>
</div>

<div class="col-xl-4 col-md-12 mb-12">
<div class="card shadow mb-4">
<div class="card-header bg-primary py-3 d-flex flex-row align-items-center justify-content-between">
<h6 class="m-0 fw-bold text-white">
Estimated queue lengths
</h6>
</h6>
</div>
<div class="card-body p-0">
<table class="table table-striped table-sm table-hover table-leftpadded mb-0" width="100%" cellspacing="0">
Expand Down Expand Up @@ -214,6 +227,8 @@

@section('js')
@vite('resources/js/chart.js')


<script>

document.addEventListener("DOMContentLoaded", function () {
Expand All @@ -223,8 +238,8 @@
ctx.canvas.width = 1000;
ctx.canvas.height = 200;

var requestData = {!! json_encode($totalRequests) !!}
var requestData = {!! json_encode($totalRequests) !!}

var color = Chart.helpers.color;
var cfg = {
type: 'line',
Expand Down Expand Up @@ -253,7 +268,7 @@
type: 'time',
time: {
unit: 'month',
tooltipFormat:'DD/MM/YYYY',
tooltipFormat:'DD/MM/YYYY',
},
ticks: {
major: {
Expand Down Expand Up @@ -421,7 +436,7 @@
label: 'C3',
backgroundColor: 'rgb(150, 200, 100)',
data: completedRequestsData["C3"]
}, {
}, {
label: 'MAE ENGM TWR',
backgroundColor: 'rgb(25, 25, 25)',
data: completedRequestsData["MAE ENGM TWR"],
Expand Down Expand Up @@ -527,7 +542,7 @@
label: 'C3',
backgroundColor: 'rgb(150, 200, 100)',
data: closedRequestsData["C3"]
}, {
}, {
label: 'MAE ENGM TWR',
backgroundColor: 'rgb(25, 25, 25)',
data: closedRequestsData["MAE ENGM TWR"],
Expand Down Expand Up @@ -597,6 +612,64 @@
});
</script>

<script>
document.addEventListener("DOMContentLoaded", function () {

// Original object from PHP
var newRequestsData = {!! json_encode($sessionsPerRating) !!};

// Build labels for last 7 months
var monthLabels = [];
var monthNumbers = [];
for (var i = 6; i >= 0; i--) {
var m = moment().subtract(i, "month");
monthLabels.push(m.format('MMMM'));
monthNumbers.push(m.month() + 1); // moment months are 0-based
}

// Convert the object to arrays per dataset
var datasets = [];
var colors = {
'S1': 'rgb(250, 150, 150)',
'S2': 'rgb(200, 100, 100)',
'S3': 'rgb(100, 100, 200)',
'C1': 'rgb(100, 200, 100)',
'C3': 'rgb(150, 200, 100)'
};

Object.keys(newRequestsData).forEach(function (rating) {
var values = newRequestsData[rating].map(v => parseFloat(v) || 0);

// If data has exactly 7 elements, use it directly
if (values.length === 7 && colors[rating]) {
datasets.push({
label: rating,
backgroundColor: colors[rating],
data: values
});
}
});

var barChartData = {
labels: monthLabels,
datasets: datasets
};

var mix = document.getElementById("sessionsPerRating").getContext('2d');
new Chart(mix, {
type: 'bar',
data: barChartData,
options: {
responsive: true,
scales: {
x: { stacked: true },
y: { stacked: true, ticks: { stepSize: 1 } }
}
}
});
});
</script>

<script>

document.addEventListener("DOMContentLoaded", function () {
Expand Down Expand Up @@ -650,4 +723,4 @@
});
</script>

@endsection
@endsection
Loading