Initial commit for new repo
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 1m4s
All checks were successful
Scan for leaked secrets using Kingfisher / kingfisher-secrets-scan (push) Successful in 1m4s
This commit is contained in:
344
app/Filament/Widgets/CumulativeChart.php
Normal file
344
app/Filament/Widgets/CumulativeChart.php
Normal file
@@ -0,0 +1,344 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Models\Line;
|
||||
use App\Models\ProductionQuantity;
|
||||
use App\Models\QualityValidation;
|
||||
use Filament\Widgets\ChartWidget;
|
||||
use Filament\Support\RawJs;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class CumulativeChart extends ChartWidget
|
||||
{
|
||||
protected static ?string $heading = 'Production Line Count';
|
||||
|
||||
//protected $listeners = ['cumulativeChart'];
|
||||
|
||||
protected static ?string $maxHeight = '400px';
|
||||
|
||||
|
||||
protected int|string|array $columnSpan = 12;
|
||||
|
||||
|
||||
// protected function getData(): array
|
||||
// {
|
||||
// $selectedPlant = session('selected_plant');
|
||||
// $activeFilter = $this->filter;
|
||||
|
||||
// // Define date range based on filter
|
||||
// switch ($activeFilter) {
|
||||
// case 'yesterday':
|
||||
// $startDate = now()->subDay()->startOfDay();
|
||||
// $endDate = now()->subDay()->endOfDay();
|
||||
// break;
|
||||
// case 'this_week':
|
||||
// $startDate = now()->startOfWeek();
|
||||
// $endDate = now()->endOfWeek();
|
||||
// break;
|
||||
// case 'this_month':
|
||||
// $startDate = now()->startOfMonth();
|
||||
// $endDate = now()->endOfMonth();
|
||||
// break;
|
||||
// default: // today
|
||||
// $startDate = now()->startOfDay();
|
||||
// $endDate = now()->endOfDay();
|
||||
// break;
|
||||
// }
|
||||
|
||||
// // Get all lines for selected plant
|
||||
// $lines = Line::where('plant_id', $selectedPlant)
|
||||
// ->pluck('name', 'id')
|
||||
// ->toArray();
|
||||
|
||||
// // Get total production per line in the date range
|
||||
// $production = \DB::table('production_quantities')
|
||||
// ->select('line_id', \DB::raw('COUNT(*) as total_quantity'))
|
||||
// ->whereBetween('created_at', [$startDate, $endDate])
|
||||
// ->whereIn('line_id', array_keys($lines))
|
||||
// ->groupBy('line_id')
|
||||
// ->pluck('total_quantity', 'line_id')
|
||||
// ->toArray();
|
||||
|
||||
// // Match quantities with lines (fill 0 if missing)
|
||||
// $labels = [];
|
||||
// $data = [];
|
||||
|
||||
// foreach ($lines as $lineId => $lineName) {
|
||||
// $labels[] = $lineName;
|
||||
// $data[] = $production[$lineId] ?? 0;
|
||||
// }
|
||||
|
||||
// return [
|
||||
// 'labels' => $labels,
|
||||
// 'datasets' => [
|
||||
// [
|
||||
// 'label' => match ($activeFilter) {
|
||||
// 'yesterday' => "Production Quantity (Yesterday)",
|
||||
// 'this_week' => "Daily Production This Week",
|
||||
// 'this_month' => "Weekly Production This Month",
|
||||
// default => "Today's Production",
|
||||
// },
|
||||
// 'data' => $data,
|
||||
// ],
|
||||
// ],
|
||||
// ];
|
||||
// }
|
||||
|
||||
|
||||
|
||||
protected function getData(): array
|
||||
{
|
||||
$selectedPlant = session('selected_plant');
|
||||
// $selectedStatus = session('success_status');
|
||||
$activeFilter = $this->filter;
|
||||
|
||||
// if (!$selectedPlant || !$selectedStatus) {
|
||||
// return [
|
||||
// 'labels' => [],
|
||||
// 'datasets' => [],
|
||||
// ];
|
||||
// }
|
||||
if (!$selectedPlant) {
|
||||
return [
|
||||
'labels' => [],
|
||||
'datasets' => [],
|
||||
];
|
||||
}
|
||||
|
||||
switch ($activeFilter)
|
||||
{
|
||||
case 'yesterday':
|
||||
$startDate = now()->subDay()->setTime(8, 0, 0);
|
||||
$endDate = now()->setTime(8, 0, 0);
|
||||
$groupBy = 'EXTRACT(HOUR FROM created_at)';
|
||||
break;
|
||||
|
||||
case 'this_week':
|
||||
$startDate = now()->startOfWeek()->setTime(8, 0, 0);
|
||||
$endDate = now()->endOfWeek()->addDay()->setTime(8, 0, 0);
|
||||
$groupBy = 'EXTRACT(DOW FROM created_at)';
|
||||
break;
|
||||
|
||||
case 'this_month':
|
||||
$startDate = now()->startOfMonth()->setTime(8, 0, 0);
|
||||
$endDate = now()->endOfMonth()->addDay()->setTime(8, 0, 0);
|
||||
$groupBy = "FLOOR((EXTRACT(DAY FROM created_at) - 1) / 7) + 1";
|
||||
break;
|
||||
|
||||
default: // today
|
||||
$startDate = now()->setTime(8, 0, 0);
|
||||
$endDate = now()->copy()->addDay()->setTime(8, 0, 0);
|
||||
$groupBy = 'EXTRACT(HOUR FROM created_at)';
|
||||
break;
|
||||
}
|
||||
|
||||
// Get lines with names and types
|
||||
$lines = Line::where('plant_id', $selectedPlant)->get(['id', 'name', 'type']);
|
||||
|
||||
$lineNames = [];
|
||||
$fgLineIds = [];
|
||||
$nonFgLineIds = [];
|
||||
|
||||
foreach ($lines as $line)
|
||||
{
|
||||
$lineNames[$line->id] = $line->name;
|
||||
|
||||
if ($line->type == 'FG Line')
|
||||
{
|
||||
$fgLineIds[] = $line->id;
|
||||
}
|
||||
else
|
||||
{
|
||||
$nonFgLineIds[] = $line->id;
|
||||
}
|
||||
}
|
||||
|
||||
//FG production from quality_validations
|
||||
$fgProduction = QualityValidation::select('line_id', \DB::raw('COUNT(*) as total_quantity'))
|
||||
// ->whereBetween('created_at', [$startDate, $endDate])
|
||||
->where('created_at', '>=', $startDate)
|
||||
->where('created_at', '<', $endDate)
|
||||
->whereIn('line_id', $fgLineIds)
|
||||
->groupBy('line_id')
|
||||
->pluck('total_quantity', 'line_id')
|
||||
->toArray();
|
||||
|
||||
$nonFgQuery = ProductionQuantity::select('line_id', \DB::raw('COUNT(*) as total_quantity'))
|
||||
->whereBetween('created_at', [$startDate, $endDate])
|
||||
->whereIn('line_id', $nonFgLineIds);
|
||||
|
||||
// if ($selectedStatus) {
|
||||
// $nonFgQuery->where('success_status', $selectedStatus);
|
||||
// }
|
||||
|
||||
$nonFgProduction = $nonFgQuery->groupBy('line_id')
|
||||
->pluck('total_quantity', 'line_id')
|
||||
->toArray();
|
||||
|
||||
|
||||
//Non-FG production from production_quantities
|
||||
// $nonFgProduction = ProductionQuantity::select('line_id', \DB::raw('COUNT(*) as total_quantity'))
|
||||
// ->whereBetween('created_at', [$startDate, $endDate])
|
||||
// ->whereIn('line_id', $nonFgLineIds)
|
||||
// ->groupBy('line_id')
|
||||
// ->pluck('total_quantity', 'line_id')
|
||||
// ->toArray();
|
||||
|
||||
//Separate FG and non-FG into different datasets
|
||||
$labels = [];
|
||||
$fgData = [];
|
||||
$nonFgData = [];
|
||||
|
||||
foreach ($lineNames as $lineId => $lineName) {
|
||||
$labels[] = $lineName;
|
||||
|
||||
if (in_array($lineId, $fgLineIds))
|
||||
{
|
||||
$fgData[] = $fgProduction[$lineId] ?? 0;
|
||||
$nonFgData[] = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
$nonFgData[] = $nonFgProduction[$lineId] ?? 0;
|
||||
$fgData[] = null;
|
||||
}
|
||||
}
|
||||
|
||||
$nonFgData = array_map(function ($value) {
|
||||
return ($value == 0 || is_null($value)) ? null : $value;
|
||||
}, $nonFgData);
|
||||
|
||||
$fgData = array_map(function ($value) {
|
||||
return ($value == 0 || is_null($value)) ? null : $value;
|
||||
}, $fgData);
|
||||
|
||||
|
||||
return [
|
||||
'labels' => $labels,
|
||||
'datasets' => array_filter([
|
||||
[
|
||||
'label' => match ($activeFilter) {
|
||||
'yesterday' => "Production Quantity (Yesterday)",
|
||||
'this_week' => "Daily Production This Week",
|
||||
'this_month' => "Weekly Production This Month",
|
||||
default => "Today's Production",
|
||||
},
|
||||
'data' => $nonFgData,
|
||||
//'backgroundColor' => '#3b82f6', // Blue for non-FG
|
||||
],
|
||||
[
|
||||
'label' => match ($activeFilter) {
|
||||
'yesterday' => "FG Count (Yesterday)",
|
||||
'this_week' => "FG Count This Week",
|
||||
'this_month' => "FG Count This Month",
|
||||
default => "Today's FG Count",
|
||||
},
|
||||
'data' => $fgData,
|
||||
// 'backgroundColor' => '#ef4444', // Red for FG
|
||||
],
|
||||
|
||||
]),
|
||||
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'bar';
|
||||
}
|
||||
|
||||
protected function getFilters(): ?array
|
||||
{
|
||||
return [
|
||||
'today' => 'Today',
|
||||
'yesterday' => 'Yesterday',
|
||||
'this_week'=> 'This Week',
|
||||
'this_month'=> 'This Month',
|
||||
];
|
||||
}
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
// 'plugins' => [
|
||||
// 'datalabels' => false,
|
||||
// ],
|
||||
'plugins' => [
|
||||
'datalabels' => [
|
||||
'anchor' => 'start', // Positions the label relative to the top of the bar
|
||||
'align' => 'start', // Aligns the label above the bar
|
||||
'offset' => -15, // Adjust if needed (positive moves up, negative moves down)
|
||||
'color' => '#000',
|
||||
'font' => [
|
||||
'weight' => 'bold',
|
||||
],
|
||||
'formatter' => RawJs::make('function(value) {
|
||||
return value;
|
||||
}'),
|
||||
],
|
||||
],
|
||||
'scales' => [
|
||||
'y' => [
|
||||
'beginAtZero' => true, //Start Y-axis from 0
|
||||
'ticks' => [
|
||||
'stepSize' => 0.5,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function getHeading(): HtmlString|string|null
|
||||
{
|
||||
$selectedPlant = session('selected_plant');
|
||||
|
||||
// Dynamic title and colors
|
||||
$title = 'Total Production';
|
||||
$titleColor = '#1e40af';
|
||||
$iconColor = 'text-blue-500';
|
||||
$textColor = '#b45309';
|
||||
|
||||
$iconSvg = '
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-8 h-8 ' . $iconColor . '" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 3v18h18M7 13h2v5H7zm4-8h2v13h-2zm4 5h2v8h-2z" />
|
||||
</svg>
|
||||
';
|
||||
|
||||
// Get chart data for the selected plant
|
||||
$data = $this->getData();
|
||||
$fgData = $data['datasets'][1]['data'] ?? [];
|
||||
$nonFgData = $data['datasets'][0]['data'] ?? [];
|
||||
|
||||
// Sum all non-null values for cumulative total
|
||||
$totalCount = array_sum(array_filter($fgData, fn($v) => !is_null($v)))
|
||||
+ array_sum(array_filter($nonFgData, fn($v) => !is_null($v)));
|
||||
|
||||
$count = number_format($totalCount);
|
||||
|
||||
return new HtmlString('
|
||||
<div class="flex items-center justify-between px-4 py-2 bg-white rounded-lg shadow-sm">
|
||||
<div class="flex items-center space-x-3">
|
||||
' . $iconSvg . '
|
||||
<div style="margin-left: 12px; color: ' . $textColor . '; ">
|
||||
<p style="color:' . $titleColor . '; font-weight: 600;">' . e($title) . '</p>
|
||||
<h2 class="text-3xl font-extrabold text-gray-800">' . $count . '</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
');
|
||||
}
|
||||
|
||||
|
||||
|
||||
// public static function canView(): bool
|
||||
// {
|
||||
// return request()->routeIs([
|
||||
// 'filament.pages.hourly-production',
|
||||
// 'filament.admin.resources.production-quantities.create',
|
||||
// ]);
|
||||
// }
|
||||
|
||||
}
|
||||
167
app/Filament/Widgets/GuardPatrolDayChart.php
Normal file
167
app/Filament/Widgets/GuardPatrolDayChart.php
Normal file
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Models\GuardPatrolEntry;
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
use Filament\Widgets\ChartWidget;
|
||||
use Filament\Support\RawJs;
|
||||
|
||||
class GuardPatrolDayChart extends ChartWidget
|
||||
{
|
||||
protected static ?string $heading = 'GuardPatrolDayChart';
|
||||
|
||||
protected static ?string $maxHeight = '350px';
|
||||
|
||||
protected int|string|array $columnSpan = 12;
|
||||
|
||||
protected $listeners = ['patrolEntryChart'];
|
||||
|
||||
protected function getData(): array
|
||||
{
|
||||
$selectedPlant = session('selected_plant');
|
||||
$activeFilter = $this->filter;
|
||||
|
||||
switch ($activeFilter)
|
||||
{
|
||||
case 'yesterday':
|
||||
$startDate = now()->subDay()->startOfDay();
|
||||
$endDate = now()->subDay()->endOfDay();
|
||||
break;
|
||||
case 'this_week':
|
||||
$startDate = now()->startOfWeek();
|
||||
$endDate = now()->endOfWeek();
|
||||
break;
|
||||
case 'this_month':
|
||||
$startDate = now()->startOfMonth();
|
||||
$endDate = now()->endOfMonth();
|
||||
break;
|
||||
default: // today
|
||||
$startDate = now()->startOfDay();
|
||||
$endDate = now()->endOfDay();
|
||||
break;
|
||||
}
|
||||
|
||||
$uniqueGuardNames = GuardPatrolEntry::join('guard_names', 'guard_patrol_entries.guard_name_id', '=', 'guard_names.id')
|
||||
->where('guard_patrol_entries.plant_id', $selectedPlant)
|
||||
->select('guard_names.id', 'guard_names.name')
|
||||
->groupBy('guard_names.id', 'guard_names.name')
|
||||
->orderBy('guard_names.name')
|
||||
->pluck('name')
|
||||
->toArray();
|
||||
|
||||
//dd($uniqueGuardNames);
|
||||
|
||||
$guardPatrols = GuardPatrolEntry::join('guard_names', 'guard_patrol_entries.guard_name_id', '=', 'guard_names.id')
|
||||
->where('guard_patrol_entries.plant_id', $selectedPlant)
|
||||
->whereBetween('guard_patrol_entries.patrol_time', [$startDate, $endDate])
|
||||
->select('guard_names.id', 'guard_names.name', 'guard_patrol_entries.patrol_time')
|
||||
->orderBy('guard_names.name')
|
||||
->orderBy('guard_patrol_entries.patrol_time')
|
||||
->get()
|
||||
->groupBy('name');
|
||||
|
||||
$guardTimeSums = [];
|
||||
foreach ($guardPatrols as $guardName => $patrols) {
|
||||
$totalSeconds = 0;
|
||||
$prevTime = null;
|
||||
foreach ($patrols as $patrol)
|
||||
{
|
||||
$currentTime = Carbon::parse($patrol->patrol_time);
|
||||
if ($prevTime)
|
||||
{
|
||||
$totalSeconds += abs($currentTime->diffInSeconds($prevTime));
|
||||
}
|
||||
$prevTime = $currentTime;
|
||||
}
|
||||
//$guardTimeSums[$guardName] = $totalSeconds / 60;
|
||||
//$guardTimeSums[$guardName] = round($totalSeconds / 60);
|
||||
$guardTimeSums[$guardName] = [
|
||||
'minutes' => round($totalSeconds / 60),
|
||||
'hours' => round($totalSeconds / 3600, 1),
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
$chartData = [];
|
||||
foreach ($uniqueGuardNames as $guardName)
|
||||
{
|
||||
if ($activeFilter === 'today') {
|
||||
$chartData[] = $guardTimeSums[$guardName]['minutes'] ?? 0;
|
||||
}
|
||||
elseif ($activeFilter === 'yesterday') {
|
||||
$chartData[] = $guardTimeSums[$guardName]['minutes'] ?? 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
$chartData[] = $guardTimeSums[$guardName]['hours'] ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
$chartData = array_map(function ($value) {
|
||||
return ($value == 0 || is_null($value)) ? null : $value;
|
||||
}, $chartData);
|
||||
|
||||
return [
|
||||
'labels' => array_values($uniqueGuardNames),
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => match ($activeFilter)
|
||||
{
|
||||
'yesterday' => "Patrols by Guard (Yesterday) Minutes",
|
||||
'this_week' => "Patrols by Guard (This Week) Hours",
|
||||
'this_month' => "Patrols by Guard (This Month) Hours",
|
||||
default => "Patrols by Guard (Today) Minutes",
|
||||
},
|
||||
'data' => $chartData,
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'bar';
|
||||
}
|
||||
|
||||
protected function getFilters(): ?array
|
||||
{
|
||||
return [
|
||||
'today' => 'Today',
|
||||
'yesterday' => 'Yesterday',
|
||||
'this_week'=> 'This Week',
|
||||
'this_month'=> 'This Month',
|
||||
];
|
||||
}
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'scales' => [
|
||||
'y' => [
|
||||
'beginAtZero' => true, //Start Y-axis from 0
|
||||
'ticks' => [
|
||||
'stepSize' => 5,
|
||||
],
|
||||
],
|
||||
],
|
||||
'plugins' => [
|
||||
'datalabels' => [
|
||||
'anchor' => 'start',
|
||||
'align' => 'start',
|
||||
'offset' => -15,
|
||||
'color' => '#000',
|
||||
'font' => [
|
||||
'weight' => 'bold',
|
||||
],
|
||||
'formatter' => RawJs::make('function(value) {
|
||||
return value;
|
||||
}'),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
202
app/Filament/Widgets/GuardPatrolHourlyChart.php
Normal file
202
app/Filament/Widgets/GuardPatrolHourlyChart.php
Normal file
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Models\CheckPointName;
|
||||
use App\Models\CheckPointTime;
|
||||
use App\Models\GuardPatrolEntry;
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
use Filament\Widgets\ChartWidget;
|
||||
use Illuminate\Support\Js;
|
||||
|
||||
class GuardPatrolHourlyChart extends ChartWidget
|
||||
{
|
||||
protected static ?string $heading = 'Chart';
|
||||
|
||||
protected static ?string $maxHeight = '330px';
|
||||
|
||||
protected int|string|array $columnSpan = 12;
|
||||
|
||||
|
||||
protected function getData(): array
|
||||
{
|
||||
$selectedPlant = session('selected_plant');
|
||||
$selectedDate = session('selected_date');
|
||||
$selectedGuardName = session('selected_name');
|
||||
$selectedTime = session('selected_time');
|
||||
|
||||
if (!$selectedPlant || !$selectedDate || !$selectedGuardName || empty($selectedTime)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$validSessions = session('valid_sessions', []);
|
||||
$isValid = $validSessions[$selectedTime] ?? false;
|
||||
|
||||
if (!$isValid) {
|
||||
return $this->getInvalidSessionChartData();
|
||||
}
|
||||
|
||||
$query = GuardPatrolEntry::where('plant_id', $selectedPlant)
|
||||
->where('guard_name_id', $selectedGuardName)
|
||||
->whereDate('patrol_time', $selectedDate);
|
||||
|
||||
if (!empty($selectedTime)) {
|
||||
[$startTime, $endTime] = explode(' - ', $selectedTime);
|
||||
$startDateTime = Carbon::parse($selectedDate . ' ' . $startTime);
|
||||
$endDateTime = Carbon::parse($selectedDate . ' ' . $endTime);
|
||||
$query->whereBetween('guard_patrol_entries.patrol_time', [$startDateTime, $endDateTime]);
|
||||
}
|
||||
|
||||
$patrols = $query->orderBy('patrol_time')->get();
|
||||
|
||||
if ($patrols->count() < 2) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Get all CheckPointTime records for the plant
|
||||
$checkPointTimes = CheckPointTime::where('plant_id', $selectedPlant)
|
||||
->orderBy('sequence_number')
|
||||
->get(['sequence_number', 'min_cushioning', 'max_cushioning', 'check_point1_id', 'check_point2_id']);
|
||||
|
||||
// Build the expected sequence
|
||||
$expectedSequence = [];
|
||||
foreach ($checkPointTimes as $row) {
|
||||
$expectedSequence[] = $row->check_point1_id;
|
||||
}
|
||||
if ($checkPointTimes->isNotEmpty()) {
|
||||
$expectedSequence[] = $checkPointTimes->last()->check_point2_id;
|
||||
}
|
||||
|
||||
// Prepare chart data
|
||||
$intervals = [];
|
||||
$labels = [];
|
||||
$colors = [];
|
||||
$cushioningData = $checkPointTimes->keyBy('sequence_number');
|
||||
|
||||
// Track the current position in the expected sequence
|
||||
$currentSeqIndex = 0;
|
||||
|
||||
for ($i = 0; $i < $patrols->count() - 1; $i++) {
|
||||
$current = Carbon::parse($patrols[$i]->patrol_time);
|
||||
$next = Carbon::parse($patrols[$i+1]->patrol_time);
|
||||
// $interval = $next->diffInMinutes($current, true);
|
||||
$diffInSeconds = $next->floatDiffInRealSeconds($current, true); // get float seconds
|
||||
$interval = round($diffInSeconds / 60, 2);
|
||||
$intervals[] = $interval;
|
||||
|
||||
$labels[] = "Seq " . ($i + 1);
|
||||
|
||||
// Check if current patrol's checkpoint matches the next expected
|
||||
if ($currentSeqIndex < count($expectedSequence) && $patrols[$i]->check_point_name_id == $expectedSequence[$currentSeqIndex]) {
|
||||
$currentSeqIndex++;
|
||||
// Apply cushioning logic for valid sequence
|
||||
$sequenceNumber = ($currentSeqIndex - 1) % $checkPointTimes->count() + 1;
|
||||
$cushioning = $cushioningData[$sequenceNumber] ?? null;
|
||||
if (!$cushioning) {
|
||||
$colors[] = '#cccccc';
|
||||
continue;
|
||||
}
|
||||
$min = $cushioning->min_cushioning;
|
||||
$max = $cushioning->max_cushioning;
|
||||
if ($interval < $min) {
|
||||
$colors[] = '#ffd700';
|
||||
} elseif ($interval <= $max) {
|
||||
$colors[] = '#4caf50';
|
||||
} else {
|
||||
$colors[] = '#ff4c4c';
|
||||
}
|
||||
} else {
|
||||
// Out of order: mark as red
|
||||
$colors[] = '#ff4c4c';
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'labels' => $labels,
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Interval (minutes)',
|
||||
'data' => $intervals,
|
||||
'backgroundColor' => $colors,
|
||||
'borderColor' => '#333',
|
||||
'borderWidth' => 1,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getInvalidSessionChartData(): array
|
||||
{
|
||||
return [
|
||||
'labels' => ['X'],
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Interval (minutes)',
|
||||
'data' => [0],
|
||||
'backgroundColor' => ['#ff4c4c'],
|
||||
'borderColor' => '#333',
|
||||
'borderWidth' => 1,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
|
||||
'scales' => [
|
||||
'y' => [
|
||||
'beginAtZero' => true, //Start Y-axis from 0
|
||||
'ticks' => [
|
||||
'stepSize' => 5,
|
||||
],
|
||||
],
|
||||
],
|
||||
'plugins' => [
|
||||
'datalabels' => [
|
||||
'anchor' => 'start',
|
||||
'align' => 'end',
|
||||
'color' => '#000',
|
||||
'font' => [
|
||||
'weight' => 'bold',
|
||||
],
|
||||
//'formatter' => Js::raw('function(value) { return Number(value).toFixed(2) + " min"; }'),
|
||||
//'formatter' => 'function(value) { return Number(value).toFixed(2) + " min"; }',
|
||||
'formatter' => Js::from("function(value) { return Number(value).toFixed(2) + ' min'; }"),
|
||||
//'formatter' => Js::from(new \Illuminate\Support\Stringable('function(value) { return Number(value).toFixed(2) + " min"; }')),
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// protected function getOptions(): array
|
||||
// {
|
||||
// return json_decode(json_encode([
|
||||
// 'plugins' => [
|
||||
// 'datalabels' => [
|
||||
// 'anchor' => 'end',
|
||||
// 'align' => 'start',
|
||||
// 'offset' => 4,
|
||||
// 'color' => '#000',
|
||||
// 'font' => [
|
||||
// 'weight' => 'bold',
|
||||
// ],
|
||||
// 'formatter' => null, // temporarily set null
|
||||
// ],
|
||||
// ],
|
||||
// 'scales' => [
|
||||
// 'y' => [
|
||||
// 'beginAtZero' => true,
|
||||
// ],
|
||||
// ],
|
||||
// ]), true);
|
||||
// }
|
||||
|
||||
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'bar';
|
||||
}
|
||||
}
|
||||
352
app/Filament/Widgets/InvoiceChart.php
Normal file
352
app/Filament/Widgets/InvoiceChart.php
Normal file
@@ -0,0 +1,352 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Models\InvoiceValidation;
|
||||
use App\Models\Line;
|
||||
use App\Models\Plant;
|
||||
use DB;
|
||||
use Filament\Widgets\ChartWidget;
|
||||
use Filament\Support\RawJs;
|
||||
|
||||
class InvoiceChart extends ChartWidget
|
||||
{
|
||||
protected static ?string $heading = 'Chart';
|
||||
|
||||
protected int|string|array $columnSpan = 12;
|
||||
|
||||
protected $listeners = ['invoiceChart'];
|
||||
|
||||
|
||||
protected function getData(): array
|
||||
{
|
||||
$selectedPlant = session('selec_plant');
|
||||
$selectedInvoice = session('select_invoice');
|
||||
$activeFilter = $this->filter; // Assuming filter is passed and activeFilter can be 'yesterday', 'this_week', 'this_month'
|
||||
|
||||
if (!$selectedPlant || !$selectedInvoice) {
|
||||
return [
|
||||
'datasets' => [],
|
||||
'labels' => [],
|
||||
];
|
||||
}
|
||||
|
||||
// Define the date range based on the active filter
|
||||
if ($activeFilter == 'yesterday') {
|
||||
$startDate = now()->subDay()->setTime(8, 0, 0);
|
||||
$endDate = now()->setTime(8, 0, 0);
|
||||
$groupBy = 'none'; // No grouping by hour
|
||||
} elseif ($activeFilter == 'this_week') {
|
||||
$startDate = now()->startOfWeek()->setTime(8, 0, 0);
|
||||
$endDate = now()->endOfWeek()->addDay()->setTime(8, 0, 0);
|
||||
$groupBy = 'day_of_week';
|
||||
} elseif ($activeFilter == 'this_month') {
|
||||
$startDate = now()->startOfMonth()->setTime(8, 0, 0);
|
||||
$endDate = now()->endOfMonth()->setTime(8, 0, 0);
|
||||
$groupBy = 'week_of_month';
|
||||
} else {
|
||||
$startDate = now()->setTime(8, 0, 0);
|
||||
$endDate = now()->copy()->addDay()->setTime(8, 0, 0);
|
||||
$groupBy = 'none'; // No grouping by hour
|
||||
}
|
||||
|
||||
// Get the counts for Imported Invoices (unique invoice numbers) and Completed Invoices
|
||||
if ($selectedInvoice == 'individual_material')
|
||||
{
|
||||
$importedInvoicesCount = InvoiceValidation::where('plant_id', $selectedPlant)
|
||||
->where('quantity', 1)
|
||||
->whereBetween('created_at', [$startDate, $endDate]) // Filter by date range
|
||||
->distinct('invoice_number') // Only distinct invoice numbers
|
||||
->count('invoice_number');
|
||||
|
||||
$completedInvoicesCount = InvoiceValidation::select('invoice_number')
|
||||
->where('plant_id', $selectedPlant)
|
||||
->where('quantity', 1)
|
||||
->whereBetween('updated_at', [$startDate, $endDate])
|
||||
->groupBy('invoice_number')
|
||||
->havingRaw(
|
||||
"COUNT(*) = SUM(CASE WHEN serial_number IS NOT NULL AND serial_number != '' THEN 1 ELSE 0 END)"
|
||||
)
|
||||
->count();
|
||||
}
|
||||
elseif ($selectedInvoice == 'serial_invoice')
|
||||
{
|
||||
$importedInvoicesCount = InvoiceValidation::where('plant_id', $selectedPlant)
|
||||
->whereNull('quantity')
|
||||
->whereBetween('created_at', [$startDate, $endDate])
|
||||
->distinct('invoice_number')
|
||||
->count('invoice_number');
|
||||
|
||||
$completedInvoicesCount = InvoiceValidation::select('invoice_number')
|
||||
->where('plant_id', $selectedPlant)
|
||||
->whereNull('quantity')
|
||||
->whereBetween('updated_at', [$startDate, $endDate])
|
||||
->groupBy('invoice_number')
|
||||
->havingRaw(
|
||||
"COUNT(*) = SUM(CASE WHEN scanned_status = 'Scanned' THEN 1 ELSE 0 END)"
|
||||
)
|
||||
->count();
|
||||
|
||||
}
|
||||
elseif ($selectedInvoice == 'bundle_material')
|
||||
{
|
||||
$importedInvoicesCount = InvoiceValidation::where('plant_id', $selectedPlant)
|
||||
->where('quantity', '>', 1)
|
||||
->whereBetween('created_at', [$startDate, $endDate]) // Filter by date range
|
||||
->distinct('invoice_number') // Only distinct invoice numbers
|
||||
->count('invoice_number');
|
||||
|
||||
$completedInvoicesCount = InvoiceValidation::select('invoice_number')
|
||||
->where('plant_id', $selectedPlant)
|
||||
->where('quantity', '>', 1)
|
||||
->whereBetween('updated_at', [$startDate, $endDate])
|
||||
->groupBy('invoice_number')
|
||||
->havingRaw(
|
||||
"COUNT(*) = SUM(CASE WHEN serial_number IS NOT NULL AND serial_number != '' THEN 1 ELSE 0 END)"
|
||||
)
|
||||
->count();
|
||||
}
|
||||
|
||||
// Prepare the chart data
|
||||
$labels = []; // Labels for each bar
|
||||
$datasets = []; // Datasets for the chart
|
||||
|
||||
if (in_array($activeFilter, ['yesterday'])) {
|
||||
$labels = ['Imported Invoice', 'Completed Invoice'];
|
||||
$datasets = [[
|
||||
'label' => 'Invoices',
|
||||
'data' => [$importedInvoicesCount, $completedInvoicesCount],
|
||||
'backgroundColor' => ['rgba(75, 192, 192, 1)', 'rgba(23, 211, 80, 1)'],
|
||||
'fill' => false,
|
||||
]];
|
||||
}
|
||||
|
||||
elseif ($activeFilter == 'this_week')
|
||||
{
|
||||
$daysOfWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
||||
$importedInvoicesPerDay = array_fill(0, 7, 0);
|
||||
$completedInvoicesPerDay = array_fill(0, 7, 0);
|
||||
|
||||
for ($index = 0; $index < 7; $index++) {
|
||||
$dayStart = now()->startOfWeek()->addDays($index)->setTime(8, 0, 0);
|
||||
$dayEnd = $dayStart->copy()->addDay()->setTime(8, 0, 0);
|
||||
|
||||
$query = InvoiceValidation::where('plant_id', $selectedPlant);
|
||||
|
||||
// Build completedQuery with only 'invoice_number' in select
|
||||
$completedQuery = InvoiceValidation::select('invoice_number')
|
||||
->where('plant_id', $selectedPlant);
|
||||
|
||||
if ($selectedInvoice == 'individual_material')
|
||||
{
|
||||
$query->where('quantity', 1)->whereBetween('created_at', [$dayStart, $dayEnd]);
|
||||
|
||||
$completedQuery->where('quantity', 1)
|
||||
->whereBetween('updated_at', [$dayStart, $dayEnd])
|
||||
->groupBy('invoice_number')
|
||||
->havingRaw(
|
||||
"COUNT(*) = SUM(CASE WHEN serial_number IS NOT NULL AND serial_number != '' THEN 1 ELSE 0 END)"
|
||||
);
|
||||
}
|
||||
elseif ($selectedInvoice == 'serial_invoice')
|
||||
{
|
||||
$query->whereNull('quantity')->whereBetween('created_at', [$dayStart, $dayEnd]);
|
||||
|
||||
$completedQuery->whereNull('quantity')
|
||||
->whereBetween('updated_at', [$dayStart, $dayEnd])
|
||||
->groupBy('invoice_number')
|
||||
->havingRaw(
|
||||
"COUNT(*) = SUM(CASE WHEN scanned_status = 'Scanned' THEN 1 ELSE 0 END)"
|
||||
);
|
||||
}
|
||||
elseif ($selectedInvoice == 'bundle_material')
|
||||
{
|
||||
$query->where('quantity', '>', 1)->whereBetween('created_at', [$dayStart, $dayEnd]);
|
||||
|
||||
$completedQuery->where('quantity', '>', 1)
|
||||
->whereBetween('updated_at', [$dayStart, $dayEnd])
|
||||
->groupBy('invoice_number')
|
||||
->havingRaw(
|
||||
"COUNT(*) = SUM(CASE WHEN serial_number IS NOT NULL AND serial_number != '' THEN 1 ELSE 0 END)"
|
||||
);
|
||||
}
|
||||
|
||||
// Imported invoices count (distinct invoice_number)
|
||||
$importedInvoicesPerDay[$index] = $query->distinct('invoice_number')->count('invoice_number');
|
||||
|
||||
$completedInvoicesPerDay[$index] = $completedQuery->count();
|
||||
}
|
||||
|
||||
$labels = $daysOfWeek;
|
||||
$datasets = [
|
||||
[
|
||||
'label' => 'Imported Invoices',
|
||||
'data' => $importedInvoicesPerDay,
|
||||
'backgroundColor' => 'rgba(75, 192, 192, 1)',
|
||||
],
|
||||
[
|
||||
'label' => 'Completed Invoices',
|
||||
'data' => $completedInvoicesPerDay,
|
||||
'backgroundColor' => 'rgba(23, 211, 80, 1)',
|
||||
],
|
||||
];
|
||||
}
|
||||
elseif ($activeFilter == 'this_month') {
|
||||
$startOfMonth = now()->startOfMonth()->setTime(8, 0, 0);
|
||||
$endOfMonth = now()->endOfMonth()->addDay()->setTime(23, 59, 59); // include full last day
|
||||
$monthName = $startOfMonth->format('M');
|
||||
|
||||
// Prepare weekly labels like "May(1-7)", "May(8-14)", etc.
|
||||
$labels = [];
|
||||
$weekStart = $startOfMonth->copy();
|
||||
$importedInvoicesPerWeek = [];
|
||||
$completedInvoicesPerWeek = [];
|
||||
|
||||
$weekIndex = 0;
|
||||
while ($weekStart < $endOfMonth) {
|
||||
$weekEnd = $weekStart->copy()->addDays(7);
|
||||
$startDay = $weekStart->format('j');
|
||||
$weekEndLimit = $weekEnd->copy()->subDay();
|
||||
$actualEnd = $weekEndLimit->greaterThan($endOfMonth) ? $endOfMonth : $weekEndLimit;
|
||||
$endDay = $actualEnd->format('j');
|
||||
|
||||
$labels[] = "{$monthName}({$startDay}-{$endDay})";
|
||||
|
||||
$queryImported = InvoiceValidation::where('plant_id', $selectedPlant)
|
||||
->whereBetween('created_at', [$weekStart, $weekEnd]);
|
||||
|
||||
if ($selectedInvoice == 'individual_material')
|
||||
{
|
||||
$queryImported->where('quantity', 1);
|
||||
}
|
||||
elseif ($selectedInvoice == 'serial_invoice')
|
||||
{
|
||||
$queryImported->whereNull('quantity');
|
||||
}
|
||||
elseif ($selectedInvoice == 'bundle_material')
|
||||
{
|
||||
$queryImported->where('quantity', '>', 1);
|
||||
}
|
||||
|
||||
$importedInvoicesPerWeek[$weekIndex] = $queryImported->distinct('invoice_number')->count('invoice_number');
|
||||
|
||||
// --- Completed ---
|
||||
$queryCompleted = InvoiceValidation::select('invoice_number')
|
||||
->where('plant_id', $selectedPlant)
|
||||
->whereBetween('updated_at', [$weekStart, $weekEnd]);
|
||||
|
||||
if ($selectedInvoice == 'individual_material') {
|
||||
$queryCompleted->where('quantity', 1)
|
||||
->groupBy('invoice_number')
|
||||
->havingRaw("COUNT(*) = SUM(CASE WHEN serial_number IS NOT NULL AND serial_number != '' THEN 1 ELSE 0 END)");
|
||||
} elseif ($selectedInvoice == 'serial_invoice') {
|
||||
$queryCompleted->whereNull('quantity')
|
||||
->groupBy('invoice_number')
|
||||
->havingRaw("COUNT(*) = SUM(CASE WHEN scanned_status = 'Scanned' THEN 1 ELSE 0 END)");
|
||||
} elseif ($selectedInvoice == 'bundle_material') {
|
||||
$queryCompleted->where('quantity', '>', 1)
|
||||
->groupBy('invoice_number')
|
||||
->havingRaw("COUNT(*) = SUM(CASE WHEN serial_number IS NOT NULL AND serial_number != '' THEN 1 ELSE 0 END)");
|
||||
}
|
||||
|
||||
$completedInvoicesPerWeek[$weekIndex] = $queryCompleted->count();
|
||||
|
||||
// Move to next week
|
||||
$weekStart = $weekEnd;
|
||||
$weekIndex++;
|
||||
}
|
||||
|
||||
$datasets = [
|
||||
[
|
||||
'label' => 'Imported Invoices',
|
||||
'data' => $importedInvoicesPerWeek,
|
||||
'backgroundColor' => 'rgba(75, 192, 192, 1)',
|
||||
],
|
||||
[
|
||||
'label' => 'Completed Invoices',
|
||||
'data' => $completedInvoicesPerWeek,
|
||||
'backgroundColor' => 'rgba(23, 211, 80, 1)',
|
||||
],
|
||||
];
|
||||
}
|
||||
else
|
||||
{
|
||||
$labels = ['Imported Invoice', 'Completed Invoice'];
|
||||
$datasets = [[
|
||||
'label' => 'Invoices',
|
||||
'data' => [$importedInvoicesCount, $completedInvoicesCount],
|
||||
'backgroundColor' => ['rgba(75, 192, 192, 1)', 'rgba(23, 211, 80, 1)'],
|
||||
'fill' => false,
|
||||
]];
|
||||
}
|
||||
|
||||
foreach ($datasets as &$dataset)
|
||||
{
|
||||
$dataset['data'] = array_map(function ($value) {
|
||||
return ($value == 0 || is_null($value)) ? null : $value;
|
||||
}, $dataset['data']);
|
||||
}
|
||||
|
||||
return [
|
||||
'datasets' => $datasets,
|
||||
'labels' => $labels,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'bar';
|
||||
}
|
||||
|
||||
public static function getDefaultName(): string
|
||||
{
|
||||
return 'invoice-chart';
|
||||
}
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'plugins' => [
|
||||
'datalabels' => [
|
||||
'anchor' => 'start',
|
||||
'align' => 'start',
|
||||
'offset' => -15,
|
||||
'color' => '#000',
|
||||
'font' => [
|
||||
'weight' => 'bold',
|
||||
],
|
||||
'formatter' => RawJs::make('function(value) {
|
||||
return value;
|
||||
}'),
|
||||
],
|
||||
],
|
||||
'scales' => [
|
||||
'y' => [
|
||||
'beginAtZero' => true,
|
||||
'ticks' => [
|
||||
'stepSize' => 1,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFilters(): ?array
|
||||
{
|
||||
return [
|
||||
'today' => 'Today',
|
||||
'yesterday' => 'Yesterday',
|
||||
'this_week'=> 'This Week',
|
||||
'this_month'=> 'This Month',
|
||||
];
|
||||
}
|
||||
|
||||
public static function canView(): bool
|
||||
{
|
||||
// dd('Checking route:', request()->route()->getName());
|
||||
// to avoid showing the widget in other pages
|
||||
return request()->routeIs('filament.admin.pages.invoice-dashboard');
|
||||
}
|
||||
|
||||
}
|
||||
319
app/Filament/Widgets/InvoiceDataChart.php
Normal file
319
app/Filament/Widgets/InvoiceDataChart.php
Normal file
@@ -0,0 +1,319 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Models\InvoiceDataValidation;
|
||||
use App\Models\InvoiceOutValidation;
|
||||
use App\Models\InvoiceValidation;
|
||||
use Filament\Widgets\ChartWidget;
|
||||
use Filament\Support\RawJs;
|
||||
|
||||
class InvoiceDataChart extends ChartWidget
|
||||
{
|
||||
protected static ?string $heading = 'Chart';
|
||||
|
||||
protected function getData(): array
|
||||
{
|
||||
$selectedPlant = session('selected_plant');
|
||||
$selectedDistribution = session(key: 'dist_channel');
|
||||
$activeFilter = $this->filter;
|
||||
|
||||
if (!$selectedPlant || !$selectedDistribution) {
|
||||
return [
|
||||
'datasets' => [],
|
||||
'labels' => [],
|
||||
];
|
||||
}
|
||||
|
||||
if ($activeFilter == 'yesterday') {
|
||||
$startDate = now()->subDay()->setTime(8, 0, 0);
|
||||
$endDate = now()->setTime(8, 0, 0);
|
||||
$groupBy = 'none'; // No grouping by hour
|
||||
} elseif ($activeFilter == 'this_week') {
|
||||
$startDate = now()->startOfWeek()->setTime(8, 0, 0);
|
||||
$endDate = now()->endOfWeek()->addDay()->setTime(8, 0, 0);
|
||||
$groupBy = 'day_of_week';
|
||||
} elseif ($activeFilter == 'this_month') {
|
||||
$startDate = now()->startOfMonth()->setTime(8, 0, 0);
|
||||
$endDate = now()->endOfMonth()->setTime(8, 0, 0);
|
||||
$groupBy = 'week_of_month';
|
||||
} else {
|
||||
$startDate = now()->setTime(8, 0, 0);
|
||||
$endDate = now()->copy()->addDay()->setTime(8, 0, 0);
|
||||
$groupBy = 'none';
|
||||
}
|
||||
|
||||
if ($selectedDistribution == 'Direct Sale')
|
||||
{
|
||||
|
||||
$totalInvoices = InvoiceDataValidation::where('plant_id', $selectedPlant)
|
||||
->where('distribution_channel_desc', $selectedDistribution)
|
||||
->whereBetween('document_date', [$startDate, $endDate])
|
||||
->distinct('document_number')
|
||||
->pluck('document_number')
|
||||
->toArray();
|
||||
|
||||
if (empty($totalInvoices)) {
|
||||
return [
|
||||
'labels' => ['Total', 'Went Out', 'Pending'],
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Invoice Count',
|
||||
'data' => [0, 0, 0],
|
||||
'backgroundColor' => ['#60A5FA', '#34D399', '#F87171'],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$wentOutInvoices = InvoiceOutValidation::where('plant_id', $selectedPlant)
|
||||
->whereIn('qr_code', $totalInvoices)
|
||||
->whereBetween('scanned_at', [$startDate, $endDate])
|
||||
->distinct('qr_code')
|
||||
->pluck('qr_code')
|
||||
->toArray();
|
||||
|
||||
$totalCount = count($totalInvoices);
|
||||
$wentOutCount = count($wentOutInvoices);
|
||||
$pendingCount = $totalCount - $wentOutCount;
|
||||
|
||||
return [
|
||||
'labels' => ['Total Invoices', 'Went Out', 'Pending'],
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => ucfirst(str_replace('_', ' ', $activeFilter)),
|
||||
'data' => [$totalCount, $wentOutCount, $pendingCount],
|
||||
'backgroundColor' => ['#60A5FA', '#34D399', '#F87171'], // blue, green, red
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
elseif ($selectedDistribution == 'Branch Sale')
|
||||
{
|
||||
$totalInvoices = InvoiceDataValidation::where('plant_id', $selectedPlant)
|
||||
->where('distribution_channel_desc', $selectedDistribution)
|
||||
->whereBetween('document_date', [$startDate, $endDate])
|
||||
->distinct('document_number')
|
||||
->pluck('document_number')
|
||||
->toArray();
|
||||
|
||||
if (empty($totalInvoices)) {
|
||||
return [
|
||||
'labels' => ['Total', 'Went Out', 'Pending'],
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Invoice Count',
|
||||
'data' => [0, 0, 0],
|
||||
'backgroundColor' => ['#60A5FA', '#34D399', '#F87171'],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$wentOutInvoices = InvoiceOutValidation::where('plant_id', $selectedPlant)
|
||||
->whereIn('qr_code', $totalInvoices)
|
||||
->whereBetween('scanned_at', [$startDate, $endDate])
|
||||
->distinct('qr_code')
|
||||
->pluck('qr_code')
|
||||
->toArray();
|
||||
|
||||
$totalCount = count($totalInvoices);
|
||||
$wentOutCount = count($wentOutInvoices);
|
||||
$pendingCount = $totalCount - $wentOutCount;
|
||||
|
||||
return [
|
||||
'labels' => ['Total Invoices', 'Went Out', 'Pending'],
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => ucfirst(str_replace('_', ' ', $activeFilter)),
|
||||
'data' => [$totalCount, $wentOutCount, $pendingCount],
|
||||
'backgroundColor' => ['#60A5FA', '#34D399', '#F87171'], // blue, green, red
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
elseif ($selectedDistribution == 'Internal Transfer')
|
||||
{
|
||||
$totalInvoices = InvoiceDataValidation::where('plant_id', $selectedPlant)
|
||||
->where('distribution_channel_desc', $selectedDistribution)
|
||||
->whereBetween('document_date', [$startDate, $endDate])
|
||||
->distinct('document_number')
|
||||
->pluck('document_number')
|
||||
->toArray();
|
||||
|
||||
if (empty($totalInvoices)) {
|
||||
return [
|
||||
'labels' => ['Total', 'Went Out', 'Pending'],
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Invoice Count',
|
||||
'data' => [0, 0, 0],
|
||||
'backgroundColor' => ['#60A5FA', '#34D399', '#F87171'],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$wentOutInvoices = InvoiceOutValidation::where('plant_id', $selectedPlant)
|
||||
->whereIn('qr_code', $totalInvoices)
|
||||
->whereBetween('scanned_at', [$startDate, $endDate])
|
||||
->distinct('qr_code')
|
||||
->pluck('qr_code')
|
||||
->toArray();
|
||||
|
||||
$totalCount = count($totalInvoices);
|
||||
$wentOutCount = count($wentOutInvoices);
|
||||
$pendingCount = $totalCount - $wentOutCount;
|
||||
|
||||
return [
|
||||
'labels' => ['Total Invoices', 'Went Out', 'Pending'],
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => ucfirst(str_replace('_', ' ', $activeFilter)),
|
||||
'data' => [$totalCount, $wentOutCount, $pendingCount],
|
||||
'backgroundColor' => ['#60A5FA', '#34D399', '#F87171'], // blue, green, red
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
elseif ($selectedDistribution == 'WOS')
|
||||
{
|
||||
$totalInvoices = InvoiceDataValidation::where('plant_id', $selectedPlant)
|
||||
->where('distribution_channel_desc', $selectedDistribution)
|
||||
->whereBetween('document_date', [$startDate, $endDate])
|
||||
->distinct('document_number')
|
||||
->pluck('document_number')
|
||||
->toArray();
|
||||
|
||||
if (empty($totalInvoices)) {
|
||||
return [
|
||||
'labels' => ['Total', 'Went Out', 'Pending'],
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Invoice Count',
|
||||
'data' => [0, 0, 0],
|
||||
'backgroundColor' => ['#60A5FA', '#34D399', '#F87171'],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$wentOutInvoices = InvoiceOutValidation::where('plant_id', $selectedPlant)
|
||||
->whereIn('qr_code', $totalInvoices)
|
||||
->whereBetween('scanned_at', [$startDate, $endDate])
|
||||
->distinct('qr_code')
|
||||
->pluck('qr_code')
|
||||
->toArray();
|
||||
|
||||
$totalCount = count($totalInvoices);
|
||||
$wentOutCount = count($wentOutInvoices);
|
||||
$pendingCount = $totalCount - $wentOutCount;
|
||||
|
||||
return [
|
||||
'labels' => ['Total Invoices', 'Went Out', 'Pending'],
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => ucfirst(str_replace('_', ' ', $activeFilter)),
|
||||
'data' => [$totalCount, $wentOutCount, $pendingCount],
|
||||
'backgroundColor' => ['#60A5FA', '#34D399', '#F87171'], // blue, green, red
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
// elseif ($selectedDistribution == 'Challan')
|
||||
// {
|
||||
// $totalInvoices = InvoiceDataValidation::where('plant_id', $selectedPlant)
|
||||
// //->where('distribution_channel_desc', $selectedDistribution)
|
||||
// ->where(function ($query) {
|
||||
// $query->whereNull('distribution_channel_desc')
|
||||
// ->orWhere('distribution_channel_desc', '');
|
||||
// })
|
||||
// ->whereBetween('document_date', [$startDate, $endDate])
|
||||
// ->distinct('document_number')
|
||||
// ->pluck('document_number')
|
||||
// ->toArray();
|
||||
|
||||
// if (empty($totalInvoices)) {
|
||||
// return [
|
||||
// 'labels' => ['Total', 'Went Out', 'Pending'],
|
||||
// 'datasets' => [
|
||||
// [
|
||||
// 'label' => 'Invoice Count',
|
||||
// 'data' => [0, 0, 0],
|
||||
// 'backgroundColor' => ['#60A5FA', '#34D399', '#F87171'],
|
||||
// ],
|
||||
// ],
|
||||
// ];
|
||||
// }
|
||||
|
||||
// $wentOutInvoices = InvoiceOutValidation::where('plant_id', $selectedPlant)
|
||||
// ->whereIn('qr_code', $totalInvoices)
|
||||
// ->whereBetween('scanned_at', [$startDate, $endDate])
|
||||
// ->distinct('qr_code')
|
||||
// ->pluck('qr_code')
|
||||
// ->toArray();
|
||||
|
||||
// $totalCount = count($totalInvoices);
|
||||
// $wentOutCount = count($wentOutInvoices);
|
||||
// $pendingCount = $totalCount - $wentOutCount;
|
||||
|
||||
// return [
|
||||
// 'labels' => ['Total Invoices', 'Went Out', 'Pending'],
|
||||
// 'datasets' => [
|
||||
// [
|
||||
// 'label' => ucfirst(str_replace('_', ' ', $activeFilter)),
|
||||
// 'data' => [$totalCount, $wentOutCount, $pendingCount],
|
||||
// 'backgroundColor' => ['#60A5FA', '#34D399', '#F87171'], // blue, green, red
|
||||
// ],
|
||||
// ],
|
||||
// ];
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
protected function getFilters(): ?array
|
||||
{
|
||||
return [
|
||||
'today' => 'Today',
|
||||
'yesterday' => 'Yesterday',
|
||||
'this_week'=> 'This Week',
|
||||
'this_month'=> 'This Month',
|
||||
];
|
||||
}
|
||||
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'bar';
|
||||
}
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
// 'plugins' => [
|
||||
// 'datalabels' => false,
|
||||
// ],
|
||||
'plugins' => [
|
||||
'datalabels' => [
|
||||
'anchor' => 'start', // Positions the label relative to the top of the bar
|
||||
'align' => 'start', // Aligns the label above the bar
|
||||
'offset' => -15, // Adjust if needed (positive moves up, negative moves down)
|
||||
'color' => '#000',
|
||||
'font' => [
|
||||
'weight' => 'bold',
|
||||
],
|
||||
'formatter' => RawJs::make('function(value) {
|
||||
return value;
|
||||
}'),
|
||||
],
|
||||
],
|
||||
'scales' => [
|
||||
'y' => [
|
||||
'beginAtZero' => true, //Start Y-axis from 0
|
||||
'ticks' => [
|
||||
'stepSize' => 0.5,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
324
app/Filament/Widgets/InvoiceQuantity.php
Normal file
324
app/Filament/Widgets/InvoiceQuantity.php
Normal file
@@ -0,0 +1,324 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Models\InvoiceValidation;
|
||||
use Filament\Widgets\ChartWidget;
|
||||
use Filament\Support\RawJs;
|
||||
|
||||
class InvoiceQuantity extends ChartWidget
|
||||
{
|
||||
protected static ?string $heading = 'Chart';
|
||||
|
||||
protected int|string|array $columnSpan = 12;
|
||||
|
||||
protected $listeners = ['invoiceChart'];
|
||||
|
||||
|
||||
protected function getData(): array
|
||||
{
|
||||
$selectedPlant = session('selec_plant');
|
||||
$selectedInvoice = session('select_invoice');
|
||||
$activeFilter = $this->filter; // Assuming filter is passed and activeFilter can be 'yesterday', 'this_week', 'this_month'
|
||||
|
||||
if (!$selectedPlant || !$selectedInvoice) {
|
||||
return [
|
||||
'datasets' => [],
|
||||
'labels' => [],
|
||||
];
|
||||
}
|
||||
|
||||
// Define the date range based on the active filter
|
||||
if ($activeFilter == 'yesterday') {
|
||||
$startDate = now()->subDay()->setTime(8, 0, 0);
|
||||
$endDate = now()->setTime(8, 0, 0);
|
||||
} elseif ($activeFilter == 'this_week') {
|
||||
$startDate = now()->startOfWeek()->setTime(8, 0, 0);
|
||||
$endDate = now()->endOfWeek()->addDay()->setTime(8, 0, 0);
|
||||
} elseif ($activeFilter == 'this_month') {
|
||||
$startDate = now()->startOfMonth()->setTime(8, 0, 0);
|
||||
$endDate = now()->endOfMonth()->setTime(8, 0, 0);
|
||||
} else {
|
||||
$startDate = now()->setTime(8, 0, 0);
|
||||
$endDate = now()->copy()->addDay()->setTime(8, 0, 0);
|
||||
}
|
||||
|
||||
if ($selectedInvoice == 'individual_material')
|
||||
{
|
||||
|
||||
$totalInvoicesCount = InvoiceValidation::where('plant_id', $selectedPlant)
|
||||
->where('quantity', 1)
|
||||
->whereBetween('created_at', [$startDate, $endDate])
|
||||
->count();
|
||||
|
||||
$scannedInvoicesCount = InvoiceValidation::where('plant_id', $selectedPlant)
|
||||
->where('quantity', 1)
|
||||
->whereNotNull('serial_number')
|
||||
->where('serial_number','!=', '')
|
||||
->whereBetween('updated_at', [$startDate, $endDate])
|
||||
->count();
|
||||
|
||||
}
|
||||
elseif ($selectedInvoice == 'serial_invoice')
|
||||
{
|
||||
$totalInvoicesCount = InvoiceValidation::where('plant_id', $selectedPlant)
|
||||
->where('quantity', null)
|
||||
->whereBetween('created_at', [$startDate, $endDate])
|
||||
->count();
|
||||
|
||||
$scannedInvoicesCount = InvoiceValidation::where('plant_id', $selectedPlant)
|
||||
->where('scanned_status', 'Scanned')
|
||||
->where(function($query) {
|
||||
$query->whereNull('quantity')
|
||||
->orWhere('quantity', 0);
|
||||
})
|
||||
->whereBetween('updated_at', [$startDate, $endDate])
|
||||
->count();
|
||||
|
||||
}
|
||||
elseif ($selectedInvoice == 'bundle_material')
|
||||
{
|
||||
$totalInvoicesCount = InvoiceValidation::where('plant_id', $selectedPlant)
|
||||
->where('quantity', '>', 1)
|
||||
->whereBetween('created_at', [$startDate, $endDate])
|
||||
->count();
|
||||
|
||||
$scannedInvoicesCount = InvoiceValidation::where('plant_id', $selectedPlant)
|
||||
->where('quantity', '>', 1)
|
||||
->whereNotNull('serial_number')
|
||||
->where('serial_number','!=', '')
|
||||
->whereBetween('updated_at', [$startDate, $endDate])
|
||||
->count();
|
||||
}
|
||||
|
||||
// Prepare the chart data
|
||||
$labels = []; // Labels for each bar
|
||||
$datasets = []; // Datasets for the chart
|
||||
|
||||
if (in_array($activeFilter, ['yesterday'])) {
|
||||
$labels = ['Total Invoices', 'Scanned Invoices'];
|
||||
$datasets = [[
|
||||
'label' => 'Invoices',
|
||||
'data' => [$totalInvoicesCount, $scannedInvoicesCount],
|
||||
'backgroundColor' => ['rgba(75, 192, 192, 1)', 'rgba(23, 211, 80, 1)'],
|
||||
'fill' => false,
|
||||
]];
|
||||
}
|
||||
|
||||
elseif ($activeFilter == 'this_week')
|
||||
{
|
||||
$daysOfWeek = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
||||
$TotalInvoicesPerDay = array_fill(0, 7, 0);
|
||||
$scannedInvoicesPerDay = array_fill(0, 7, 0);
|
||||
|
||||
for ($index = 0; $index < 7; $index++) {
|
||||
$dayStart = now()->startOfWeek()->addDays($index)->setTime(8, 0, 0);
|
||||
$dayEnd = $dayStart->copy()->addDay()->setTime(8, 0, 0);
|
||||
|
||||
$query = InvoiceValidation::where('plant_id', $selectedPlant);
|
||||
|
||||
// Build completedQuery with only 'invoice_number' in select
|
||||
$completedQuery = InvoiceValidation::select('invoice_number')
|
||||
->where('plant_id', $selectedPlant);
|
||||
|
||||
if ($selectedInvoice == 'individual_material')
|
||||
{
|
||||
$query->where('quantity', 1)->whereBetween('created_at', [$dayStart, $dayEnd]);
|
||||
|
||||
$completedQuery->where('quantity', 1)
|
||||
->whereBetween('updated_at', [$dayStart, $dayEnd])
|
||||
->whereNotNull('serial_number')
|
||||
->where('serial_number', '!=', '');
|
||||
}
|
||||
elseif ($selectedInvoice == 'serial_invoice')
|
||||
{
|
||||
$query->whereNull('quantity')->whereBetween('created_at', [$dayStart, $dayEnd]);
|
||||
$completedQuery = InvoiceValidation::where('plant_id', $selectedPlant)
|
||||
->where('scanned_status', 'Scanned')
|
||||
->where(function($query) {
|
||||
$query->whereNull('quantity')
|
||||
->orWhere('quantity', 0);
|
||||
})
|
||||
->whereBetween('updated_at', [$dayStart, $dayEnd]);
|
||||
}
|
||||
elseif ($selectedInvoice == 'bundle_material')
|
||||
{
|
||||
$query->where('quantity', '>', 1)->whereBetween('created_at', [$dayStart, $dayEnd]);
|
||||
|
||||
$completedQuery->where('quantity', '>', 1)
|
||||
->whereBetween('updated_at', [$dayStart, $dayEnd])
|
||||
->whereNotNull('serial_number')
|
||||
->where('serial_number', '!=', '');
|
||||
}
|
||||
|
||||
// Imported invoices count (distinct invoice_number)
|
||||
$TotalInvoicesPerDay[$index] = $query->count();
|
||||
|
||||
$scannedInvoicesPerDay[$index] = $completedQuery->count();
|
||||
|
||||
//dd($TotalInvoicesPerDay, $scannedInvoicesPerDay);
|
||||
}
|
||||
|
||||
$labels = $daysOfWeek;
|
||||
$datasets = [
|
||||
[
|
||||
'label' => 'Total Invoices',
|
||||
'data' => $TotalInvoicesPerDay,
|
||||
'backgroundColor' => 'rgba(75, 192, 192, 1)',
|
||||
],
|
||||
[
|
||||
'label' => 'Scanned Invoices',
|
||||
'data' => $scannedInvoicesPerDay,
|
||||
'backgroundColor' => 'rgba(23, 211, 80, 1)',
|
||||
],
|
||||
];
|
||||
}
|
||||
elseif ($activeFilter == 'this_month')
|
||||
{
|
||||
$startOfMonth = now()->startOfMonth()->setTime(8, 0, 0);
|
||||
$endOfMonth = now()->endOfMonth()->addDay()->setTime(8, 0, 0); // include full last day
|
||||
$monthName = $startOfMonth->format('M');
|
||||
|
||||
// Prepare weekly labels like "May(1-7)", "May(8-14)", etc.
|
||||
$labels = [];
|
||||
$weekStart = $startOfMonth->copy();
|
||||
$TotalInvoicesPerWeek = [];
|
||||
$scannedInvoicesPerWeek = [];
|
||||
|
||||
$weekIndex = 0;
|
||||
while ($weekStart < $endOfMonth) {
|
||||
$weekEnd = $weekStart->copy()->addDays(7);
|
||||
$startDay = $weekStart->format('j');
|
||||
$weekEndLimit = $weekEnd->copy()->subDay();
|
||||
$actualEnd = $weekEndLimit->greaterThan($endOfMonth) ? $endOfMonth : $weekEndLimit;
|
||||
$endDay = $actualEnd->format('j');
|
||||
|
||||
$labels[] = "{$monthName}({$startDay}-{$endDay})";
|
||||
|
||||
$queryImported = InvoiceValidation::where('plant_id', $selectedPlant)
|
||||
->whereBetween('created_at', [$weekStart, $weekEnd]);
|
||||
|
||||
if ($selectedInvoice == 'individual_material')
|
||||
{
|
||||
$queryImported->where('quantity', 1);
|
||||
}
|
||||
elseif ($selectedInvoice == 'serial_invoice')
|
||||
{
|
||||
$queryImported->whereNull('quantity');
|
||||
}
|
||||
elseif ($selectedInvoice == 'bundle_material')
|
||||
{
|
||||
$queryImported->where('quantity', '>', 1);
|
||||
}
|
||||
|
||||
$TotalInvoicesPerWeek[$weekIndex] = $queryImported->count();
|
||||
|
||||
// --- Completed ---
|
||||
$queryCompleted = InvoiceValidation::select('invoice_number')
|
||||
->where('plant_id', $selectedPlant)
|
||||
->whereBetween('updated_at', [$weekStart, $weekEnd]);
|
||||
|
||||
if ($selectedInvoice == 'individual_material') {
|
||||
$queryCompleted->where('quantity', 1)
|
||||
->whereNotNull('serial_number')
|
||||
->where('serial_number', '!=', '');
|
||||
} elseif ($selectedInvoice == 'serial_invoice') {
|
||||
$queryCompleted->whereNull('quantity')
|
||||
->where('scanned_status', 'Scanned')
|
||||
->where(function($query) {
|
||||
$query->whereNull('quantity')
|
||||
->orWhere('quantity', 0);
|
||||
});
|
||||
} elseif ($selectedInvoice == 'bundle_material') {
|
||||
$queryCompleted->where('quantity', '>', 1)
|
||||
->whereNotNull('serial_number')
|
||||
->where('serial_number', '!=', '');
|
||||
}
|
||||
|
||||
$scannedInvoicesPerWeek[$weekIndex] = $queryCompleted->count();
|
||||
|
||||
// Move to next week
|
||||
$weekStart = $weekEnd;
|
||||
$weekIndex++;
|
||||
}
|
||||
|
||||
$datasets = [
|
||||
[
|
||||
'label' => 'Total Invoices',
|
||||
'data' => $TotalInvoicesPerWeek,
|
||||
'backgroundColor' => 'rgba(75, 192, 192, 1)',
|
||||
],
|
||||
[
|
||||
'label' => 'Scanned Invoices',
|
||||
'data' => $scannedInvoicesPerWeek,
|
||||
'backgroundColor' => 'rgba(23, 211, 80, 1)',
|
||||
],
|
||||
];
|
||||
}
|
||||
else
|
||||
{
|
||||
$labels = ['Total Invoices', 'Scanned Invoices'];
|
||||
$datasets = [[
|
||||
'label' => 'Invoices',
|
||||
'data' => [$totalInvoicesCount, $scannedInvoicesCount],
|
||||
'backgroundColor' => ['rgba(75, 192, 192, 1)', 'rgba(23, 211, 80, 1)'],
|
||||
'fill' => false,
|
||||
]];
|
||||
}
|
||||
|
||||
foreach ($datasets as &$dataset)
|
||||
{
|
||||
$dataset['data'] = array_map(function ($value) {
|
||||
return ($value == 0 || is_null($value)) ? null : $value;
|
||||
}, $dataset['data']);
|
||||
}
|
||||
|
||||
return [
|
||||
'datasets' => $datasets,
|
||||
'labels' => $labels,
|
||||
];
|
||||
}
|
||||
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'bar';
|
||||
}
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'plugins' => [
|
||||
'datalabels' => [
|
||||
'anchor' => 'start',
|
||||
'align' => 'start',
|
||||
'offset' => -15,
|
||||
'color' => '#000',
|
||||
'font' => [
|
||||
'weight' => 'bold',
|
||||
],
|
||||
'formatter' => RawJs::make('function(value) {
|
||||
return value;
|
||||
}'),
|
||||
],
|
||||
],
|
||||
'scales' => [
|
||||
'y' => [
|
||||
'beginAtZero' => true,
|
||||
'ticks' => [
|
||||
'stepSize' => 1,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFilters(): ?array
|
||||
{
|
||||
return [
|
||||
'today' => 'Today',
|
||||
'yesterday' => 'Yesterday',
|
||||
'this_week'=> 'This Week',
|
||||
'this_month'=> 'This Month',
|
||||
];
|
||||
}
|
||||
}
|
||||
299
app/Filament/Widgets/ItemOverview.php
Normal file
299
app/Filament/Widgets/ItemOverview.php
Normal file
@@ -0,0 +1,299 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Support\Assets\Js;
|
||||
use Filament\Support\RawJs;
|
||||
use Filament\Widgets\ChartWidget;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Support\HtmlString;
|
||||
|
||||
class ItemOverview extends ChartWidget
|
||||
{
|
||||
protected static ?string $heading = 'Hourly Production';
|
||||
|
||||
public $totalCount = 0;
|
||||
|
||||
|
||||
protected int|string|array $columnSpan = '12';
|
||||
|
||||
protected static ?string $maxHeight = '400px';
|
||||
|
||||
protected $listeners = ['filtersUpdated' => '$refresh'];
|
||||
|
||||
protected static string $color = 'info';
|
||||
protected static ?string $icon = 'heroicon-o-chart-bar';
|
||||
protected static ?string $iconColor = 'info';
|
||||
protected static ?string $iconBackgroundColor = 'info';
|
||||
protected static ?string $label = 'Total Production';
|
||||
protected static ?string $badgeColor = 'success';
|
||||
|
||||
protected static ?string $badgeIcon = 'heroicon-o-check-circle';
|
||||
|
||||
protected static ?string $badgeIconPosition = 'after';
|
||||
|
||||
protected static ?string $badgeSize = 'xs';
|
||||
|
||||
|
||||
public function getData(): array
|
||||
{
|
||||
$activeFilter = $this->filter;
|
||||
|
||||
$selectedPlant = session('selected_plant') ?? session('select_plant');
|
||||
$selectedLine = session('selected_line') ?? session('select_line');
|
||||
|
||||
$selectedStatus = session('selected_status');
|
||||
|
||||
if (!$selectedPlant || !$selectedLine)
|
||||
{
|
||||
return [
|
||||
'datasets' => [],
|
||||
'labels' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$this->dispatch('filtersUpdated', [
|
||||
'plant' => $selectedPlant,
|
||||
'line' => $selectedLine,
|
||||
'filter' => $activeFilter,
|
||||
]);
|
||||
|
||||
// Determine if line is FG Line
|
||||
$line = \App\Models\Line::find($selectedLine);
|
||||
$isFgLine = $line?->type == 'FG Line';
|
||||
|
||||
// Set date range and groupBy logic
|
||||
if ($activeFilter == 'yesterday')
|
||||
{
|
||||
$startDate = now()->subDay()->setTime(8, 0, 0);
|
||||
$endDate = now()->setTime(8, 0, 0);
|
||||
$groupBy = 'EXTRACT(HOUR FROM created_at)';
|
||||
}
|
||||
elseif ($activeFilter == 'this_week')
|
||||
{
|
||||
$startDate = now()->startOfWeek()->setTime(8, 0, 0);
|
||||
$endDate = now()->endOfWeek()->addDay()->setTime(8, 0, 0);
|
||||
$groupBy = 'EXTRACT(DOW FROM created_at)';
|
||||
}
|
||||
elseif ($activeFilter == 'this_month')
|
||||
{
|
||||
$startDate = now()->startOfMonth();
|
||||
$endDate = now()->endOfMonth();
|
||||
$groupBy = "FLOOR((EXTRACT(DAY FROM created_at) - 1) / 7) + 1";
|
||||
}
|
||||
else
|
||||
{
|
||||
$startDate = now()->setTime(8, 0, 0);
|
||||
$endDate = now()->copy()->addDay()->setTime(8, 0, 0);
|
||||
$groupBy = 'EXTRACT(HOUR FROM created_at)';
|
||||
}
|
||||
|
||||
$baseTable = $isFgLine ? 'quality_validations' : 'production_quantities';
|
||||
|
||||
// $query = \DB::table($baseTable)
|
||||
// ->selectRaw("$groupBy AS time_unit, COUNT(*) AS total_quantity")
|
||||
// ->where('created_at', '>=', $startDate)
|
||||
// ->where('created_at', '<', $endDate)
|
||||
// ->where('plant_id', $selectedPlant)
|
||||
// ->where('line_id', $selectedLine)
|
||||
// ->groupByRaw($groupBy)
|
||||
// ->orderByRaw($groupBy)
|
||||
// ->pluck('total_quantity', 'time_unit')
|
||||
// ->toArray();
|
||||
$query = \DB::table($baseTable)
|
||||
->selectRaw("$groupBy AS time_unit, COUNT(*) AS total_quantity")
|
||||
->where('created_at', '>=', $startDate)
|
||||
->where('created_at', '<', $endDate)
|
||||
->where('plant_id', $selectedPlant)
|
||||
->where('line_id', $selectedLine);
|
||||
|
||||
if ($selectedStatus && !$isFgLine) {
|
||||
// Only filter success_status for production_quantities (Non-FG)
|
||||
$query->where('success_status', $selectedStatus);
|
||||
}
|
||||
|
||||
$query = $query->groupByRaw($groupBy)
|
||||
->orderByRaw($groupBy)
|
||||
->pluck('total_quantity', 'time_unit')
|
||||
->toArray();
|
||||
|
||||
|
||||
if ($activeFilter == 'this_month')
|
||||
{
|
||||
$weeksCount = ceil($endDate->day / 7);
|
||||
$allWeeks = array_fill(1, $weeksCount, 0);
|
||||
$data = array_replace($allWeeks, $query);
|
||||
|
||||
$labels = [];
|
||||
for ($i = 1; $i <= $weeksCount; $i++) {
|
||||
$weekStart = $startDate->copy()->addDays(($i - 1) * 7)->format('d M');
|
||||
$weekEnd = $startDate->copy()->addDays($i * 7 - 1)->min($endDate)->format('d M');
|
||||
$labels[] = "Week $i ($weekStart - $weekEnd)";
|
||||
}
|
||||
|
||||
$orderedData = array_values($data);
|
||||
}
|
||||
elseif ($activeFilter == 'this_week')
|
||||
{
|
||||
$labels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
||||
$data = array_fill(0, 7, 0);
|
||||
foreach ($query as $dow => $count) {
|
||||
$data[$dow] = $count;
|
||||
}
|
||||
$orderedData = [
|
||||
$data[1] ?? 0, $data[2] ?? 0, $data[3] ?? 0,
|
||||
$data[4] ?? 0, $data[5] ?? 0, $data[6] ?? 0,
|
||||
$data[0] ?? 0,
|
||||
];
|
||||
}
|
||||
// else
|
||||
// {
|
||||
// $allHours = array_fill(0, 24, 0);
|
||||
// $data = array_replace($allHours, $query);
|
||||
|
||||
// $shiftedKeys = array_merge(range(8, 23), range(0, 7));
|
||||
// $orderedData = array_map(fn($hour) => $data[$hour], $shiftedKeys);
|
||||
|
||||
// $labels = array_map(fn ($hour) => date("g A", strtotime("$hour:00")), $shiftedKeys);
|
||||
// }
|
||||
|
||||
else
|
||||
{
|
||||
$allHours = array_fill(0, 24, 0);
|
||||
$data = array_replace($allHours, $query);
|
||||
|
||||
$shiftedData = [];
|
||||
foreach ($data as $hour => $count) {
|
||||
$nextHour = ($hour + 1) % 24;
|
||||
$shiftedData[$nextHour] = $count;
|
||||
}
|
||||
$shiftedKeys = array_merge(range(9, 23), range(0, 8));
|
||||
$orderedData = array_map(fn($hour) => $shiftedData[$hour] ?? 0, $shiftedKeys);
|
||||
|
||||
$labels = array_map(function($hour) {
|
||||
$prevHour = ($hour - 1 + 24) % 24;
|
||||
return sprintf("%s",
|
||||
date("g A", strtotime("$hour:00")),
|
||||
date("g", strtotime("$prevHour:00")),
|
||||
date("g A", strtotime("$hour:00"))
|
||||
);
|
||||
}, $shiftedKeys);
|
||||
}
|
||||
|
||||
$this->totalCount = array_sum($orderedData);
|
||||
|
||||
|
||||
return [
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => match ($activeFilter) {
|
||||
'this_week' => $isFgLine ? 'Daily FG Count This Week' : 'Daily Production This Week',
|
||||
'this_month' => $isFgLine ? 'Weekly FG Count This Month' : 'Weekly Production This Month',
|
||||
'yesterday' => $isFgLine ? "Yesterday's FG Count" : "Yesterday's Hourly Production",
|
||||
default => $isFgLine ? "Today's FG Count" : "Today's Hourly Production",
|
||||
},
|
||||
'data' => $orderedData,
|
||||
// 'interaction' => [
|
||||
// 'mode' => 'nearest',
|
||||
// 'axis' => 'x',
|
||||
// 'intersect' => false,
|
||||
// ],
|
||||
'borderColor' => 'rgba(75, 192, 192, 1)',
|
||||
'backgroundColor' => 'rgba(75, 192, 192, 0.2)',
|
||||
'fill' => false,
|
||||
'tension' => 0.3,
|
||||
],
|
||||
],
|
||||
'labels' => $labels,
|
||||
];
|
||||
}
|
||||
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'line';
|
||||
}
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'plugins' => [
|
||||
'datalabels' => [
|
||||
'anchor' => 'end', // Change to 'end' to align to the right edge of the bar
|
||||
'align' => 'right', // Align text to the right (optional, for label text alignment)
|
||||
'offset' => 3, // Move label to the right by ~11px (≈3mm)
|
||||
'color' => '#000',
|
||||
'font' => [
|
||||
'weight' => 'bold',
|
||||
],
|
||||
'formatter' => 'function(value) { return Number(value); }',
|
||||
],
|
||||
],
|
||||
'scales' => [
|
||||
'y' => [
|
||||
'beginAtZero' => true,
|
||||
'ticks' => [
|
||||
'stepSize' => 0.5,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function getHeading(): HtmlString|string|null
|
||||
{
|
||||
// Detect selected line & type
|
||||
$selectedLine = session('selected_line') ?? session('select_line');
|
||||
$line = \App\Models\Line::find($selectedLine);
|
||||
$isFgLine = $line?->type == 'FG Line';
|
||||
|
||||
// Dynamic title and icon
|
||||
$title = $isFgLine ? 'FG Line Production' : 'Total Production';
|
||||
$titleColor = $isFgLine ? '#16a34a' : '#1e40af';
|
||||
//$iconColor = $isFgLine ? 'text-green-500' : 'text-blue-500';
|
||||
$iconColor = $isFgLine ? 'text-green-500' : 'text-blue-500';
|
||||
//$textColor = $isFgLine ? '#16a34a' : '#3b82f6';
|
||||
$textColor = $isFgLine ? '#0f766e' : '#b45309';
|
||||
$iconSvg = '
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
class="w-8 h-8 ' . $iconColor . '" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 3v18h18M7 13h2v5H7zm4-8h2v13h-2zm4 5h2v8h-2z" />
|
||||
</svg>
|
||||
';
|
||||
|
||||
$count = number_format($this->totalCount ?? 0);
|
||||
|
||||
return new HtmlString('
|
||||
<div class="flex items-center justify-between px-4 py-2 bg-white rounded-lg shadow-sm">
|
||||
<div class="flex items-center space-x-3">
|
||||
' . $iconSvg . '
|
||||
<div style="margin-left: 12px; color: ' . $textColor . ';">
|
||||
<p style="color:' . $titleColor . '; font-weight: 600;">' . e($title) . '</p>
|
||||
<h2 class="text-3xl font-extrabold text-gray-800">' . $count . '</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
protected function getFilters(): ?array
|
||||
{
|
||||
return [
|
||||
'today' => 'Today',
|
||||
'yesterday' => 'Yesterday',
|
||||
'this_week'=> 'This Week',
|
||||
'this_month'=> 'This Month',
|
||||
];
|
||||
}
|
||||
|
||||
public static function canView(): bool
|
||||
{
|
||||
return request()->routeIs([
|
||||
'filament.pages.hourly-production',
|
||||
'filament.admin.resources.production-quantities.create',
|
||||
]);
|
||||
}
|
||||
}
|
||||
189
app/Filament/Widgets/ProductionLineStopChart.php
Normal file
189
app/Filament/Widgets/ProductionLineStopChart.php
Normal file
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use Filament\Widgets\ChartWidget;
|
||||
|
||||
class ProductionLineStopChart extends ChartWidget
|
||||
{
|
||||
protected static ?string $heading = 'Production Line Stop Chart';
|
||||
|
||||
protected static ?string $maxHeight = '250px';
|
||||
|
||||
protected function getData(): array
|
||||
{
|
||||
$activeFilter = $this->filter;
|
||||
$selectedPlant = session('selected_plant');
|
||||
$selectedLine = session('selected_line');
|
||||
|
||||
if (!$selectedPlant || !$selectedLine)
|
||||
{
|
||||
return [
|
||||
'datasets' => [],
|
||||
'labels' => [],
|
||||
];
|
||||
}
|
||||
|
||||
// Set time range based on active filter
|
||||
if ($activeFilter === 'yesterday') {
|
||||
$startDate = now()->subDay()->setTime(8, 0, 0);
|
||||
$endDate = now()->setTime(8, 0, 0);
|
||||
} elseif ($activeFilter === 'this_week') {
|
||||
$startDate = now()->startOfWeek()->setTime(8, 0, 0);
|
||||
$endDate = now()->endOfWeek()->addDay()->setTime(8, 0, 0);
|
||||
} elseif ($activeFilter === 'this_month') {
|
||||
$startDate = now()->startOfMonth();
|
||||
$endDate = now()->endOfMonth();
|
||||
} else {
|
||||
$startDate = now()->setTime(8, 0, 0);
|
||||
$endDate = now()->copy()->addDay()->setTime(8, 0, 0);
|
||||
}
|
||||
|
||||
// Fetch stop reason data
|
||||
// treats minutes as a visual decimal (e.g., 15 mins = 0.15)
|
||||
$query = \DB::table('production_line_stops')
|
||||
->join('line_stops as ls', 'production_line_stops.linestop_id', '=', 'ls.id')
|
||||
->join('lines', 'production_line_stops.line_id', '=', 'lines.id')
|
||||
->join('plants', 'production_line_stops.plant_id', '=', 'plants.id')
|
||||
->select(
|
||||
'ls.code',
|
||||
'ls.reason',
|
||||
\DB::raw("
|
||||
SUM(production_line_stops.stop_hour) as total_stop_hours,
|
||||
SUM(production_line_stops.stop_min) as total_stop_minutes
|
||||
")
|
||||
)
|
||||
->when($selectedPlant, fn($q) => $q->where('plants.id', $selectedPlant))
|
||||
->when($selectedLine, fn($q) => $q->where('lines.id', $selectedLine))
|
||||
->whereBetween('production_line_stops.created_at', [$startDate, $endDate])
|
||||
->groupBy('ls.code', 'ls.reason')
|
||||
//->orderByDesc('total_hours')
|
||||
->get();
|
||||
|
||||
// Handle empty data gracefully
|
||||
if ($query->isEmpty()) {
|
||||
return [
|
||||
'datasets' => [[
|
||||
'label' => 'Line Stop Reasons',
|
||||
'data' => [1],
|
||||
'backgroundColor' => ['rgba(200, 200, 200, 0.4)'],
|
||||
'borderColor' => ['rgba(200, 200, 200, 1)'],
|
||||
'borderWidth' => 1,
|
||||
]],
|
||||
'labels' => ['No Data'],
|
||||
];
|
||||
}
|
||||
|
||||
$labels = [];
|
||||
$data = [];
|
||||
$backgroundColors = [];
|
||||
$borderColors = [];
|
||||
|
||||
function generateColor(string $key, float $opacity): string {
|
||||
// Use a stable hash of the key, no randomness
|
||||
$hash = md5($key);
|
||||
|
||||
// Get the RGB components from the hash
|
||||
$r = hexdec(substr($hash, 0, 2)); // Red component
|
||||
$g = hexdec(substr($hash, 2, 2)); // Green component
|
||||
$b = hexdec(substr($hash, 4, 2)); // Blue component
|
||||
|
||||
// Avoid deep green colors for the `code`
|
||||
if ($g > 150 && $g < 255) {
|
||||
// Adjust green component if it's in the deep green range
|
||||
$g = 150; // Assign a fixed, safe value for green
|
||||
}
|
||||
|
||||
// Return the color as rgba with the specified opacity
|
||||
return "rgba($r, $g, $b, $opacity)";
|
||||
}
|
||||
|
||||
$totalStopMinutes = 0;
|
||||
|
||||
foreach ($query as $row) {
|
||||
$code = $row->code;
|
||||
$reason = $row->reason;
|
||||
|
||||
$stopHours = $row->total_stop_hours;
|
||||
$stopMinutes = $row->total_stop_minutes;
|
||||
$visualTotal = $stopHours + ($stopMinutes / 100);
|
||||
|
||||
$codeLabel = "$code - $reason";
|
||||
$labels[] = $codeLabel;
|
||||
$data[] = $visualTotal;
|
||||
$backgroundColors[] = generateColor($code, 0.7); // Unique color for each stop code
|
||||
$borderColors[] = generateColor($code, 1.0);
|
||||
|
||||
// Accumulate total stop time (in minutes)
|
||||
$totalStopMinutes += ($stopHours * 60) + $stopMinutes;
|
||||
}
|
||||
|
||||
// Calculate remaining time (1440 minutes = 24 hours)
|
||||
$remainingMinutes = 1440 - $totalStopMinutes;
|
||||
$runtimeHours = floor($remainingMinutes / 60);
|
||||
$runtimeMinutes = $remainingMinutes % 60;
|
||||
$runtimeVisual = $runtimeHours + ($runtimeMinutes / 100);
|
||||
|
||||
// Add runtime slice with green color (either light or dark green)
|
||||
$labels[] = 'Available Runtime';
|
||||
$data[] = $runtimeVisual;
|
||||
$backgroundColors[] = 'rgba(47, 218, 47, 0.94)'; // Green for runtime
|
||||
$borderColors[] = 'rgba(75, 192, 75, 1)';
|
||||
|
||||
return [
|
||||
'datasets' => [[
|
||||
'label' => 'Line Stop Durations (hrs)',
|
||||
'data' => $data,
|
||||
'backgroundColor' => $backgroundColors,
|
||||
'borderColor' => $borderColors,
|
||||
'borderWidth' => 1,
|
||||
]],
|
||||
'labels' => $labels,
|
||||
];
|
||||
}
|
||||
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'pie';
|
||||
}
|
||||
|
||||
protected function getFilters(): ?array
|
||||
{
|
||||
return [
|
||||
'today' => 'Today',
|
||||
'yesterday' => 'Yesterday',
|
||||
];
|
||||
}
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
// 'responsive' => true,
|
||||
// 'plugins' => [
|
||||
// 'legend' => [
|
||||
// 'position' => 'bottom',
|
||||
// ],
|
||||
// 'tooltip' => [
|
||||
// 'enabled' => true, // Tooltips enabled on hover
|
||||
// ],
|
||||
// ],
|
||||
'plugins' => [
|
||||
'datalabels' => false,
|
||||
],
|
||||
'scales' => [
|
||||
'x' => [
|
||||
'display' => false, // Disable x-axis
|
||||
],
|
||||
'y' => [
|
||||
'display' => false, // Disable y-axis
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public static function canView(): bool
|
||||
{
|
||||
// Only show on HourlyProduction page
|
||||
return request()->routeIs('filament.pages.production-line-stop-count');
|
||||
}
|
||||
}
|
||||
263
app/Filament/Widgets/ProductionOrderChart.php
Normal file
263
app/Filament/Widgets/ProductionOrderChart.php
Normal file
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Models\Line;
|
||||
use Filament\Widgets\ChartWidget;
|
||||
use Illuminate\Support\Js;
|
||||
use Filament\Support\RawJs;
|
||||
use Illuminate\Support\Stringable;
|
||||
|
||||
class ProductionOrderChart extends ChartWidget
|
||||
{
|
||||
protected static ?string $heading = 'ProductionOrderCount';
|
||||
|
||||
protected int|string|array $columnSpan = 12;
|
||||
|
||||
protected static ?string $maxHeight = '330px';
|
||||
|
||||
protected $listeners = ['productionOrderChart'];
|
||||
|
||||
|
||||
protected function getData(): array
|
||||
{
|
||||
$activeFilter = $this->filter;
|
||||
|
||||
$selectedPlant = session('selected_plant');
|
||||
$selectedLine = session('selected_line');
|
||||
$selectedStatus = session('selected_status');
|
||||
$productionOrder = session('production_order');
|
||||
|
||||
if (!$selectedPlant || !$selectedLine || !$productionOrder || !$selectedStatus) {
|
||||
return [
|
||||
'datasets' => [],
|
||||
'labels' => [],
|
||||
];
|
||||
}
|
||||
|
||||
// 1. Check line type
|
||||
$lineType = Line::where('id', $selectedLine)->value('type');
|
||||
|
||||
// 2. Set base table and columns depending on line type
|
||||
if ($lineType == 'FG Line')
|
||||
{
|
||||
$table = 'quality_validations';
|
||||
$dateColumn = 'created_at';
|
||||
$plantColumn = 'plant_id';
|
||||
$lineColumn = 'line_id';
|
||||
$orderColumn = 'production_order';
|
||||
}
|
||||
else
|
||||
{
|
||||
$table = 'production_quantities';
|
||||
$dateColumn = 'created_at';
|
||||
$plantColumn = 'plant_id';
|
||||
$lineColumn = 'line_id';
|
||||
$orderColumn = 'production_order';
|
||||
}
|
||||
|
||||
// 3. Set filter logic as before
|
||||
if ($activeFilter == 'yesterday')
|
||||
{
|
||||
$startDate = now()->subDay()->setTime(8, 0, 0);
|
||||
$endDate = now()->setTime(8, 0, 0);
|
||||
$groupBy = "EXTRACT(HOUR FROM $table.$dateColumn)";
|
||||
}
|
||||
else if ($activeFilter == 'this_week')
|
||||
{
|
||||
$startDate = now()->startOfWeek()->setTime(8, 0, 0);
|
||||
$endDate = now()->endOfWeek()->addDay()->setTime(8, 0, 0);
|
||||
$groupBy = "EXTRACT(DOW FROM $table.$dateColumn)";
|
||||
}
|
||||
else if ($activeFilter == 'this_month')
|
||||
{
|
||||
$startDate = now()->startOfMonth();
|
||||
$endDate = now()->endOfMonth();
|
||||
$groupBy = "FLOOR((EXTRACT(DAY FROM $table.$dateColumn) - 1) / 7) + 1";
|
||||
}
|
||||
else
|
||||
{
|
||||
$startDate = now()->setTime(8, 0, 0);
|
||||
$endDate = now()->copy()->addDay()->setTime(8, 0, 0);
|
||||
$groupBy = "EXTRACT(HOUR FROM $table.$dateColumn)";
|
||||
}
|
||||
|
||||
// // 4. Build the query dynamically
|
||||
// $query = \DB::table($table)
|
||||
// ->join('plants', "$table.$plantColumn", '=', 'plants.id')
|
||||
// ->join('lines', "$table.$lineColumn", '=', 'lines.id')
|
||||
// ->selectRaw("$groupBy AS time_unit, count(*) AS total_quantity")
|
||||
// ->whereBetween("$table.$dateColumn", [$startDate, $endDate])
|
||||
// ->where('plants.id', $selectedPlant)
|
||||
// ->where('lines.id', $selectedLine)
|
||||
// ->when($productionOrder, fn($q) => $q->where("$table.$orderColumn", $productionOrder))
|
||||
// ->groupByRaw($groupBy)
|
||||
// ->orderByRaw($groupBy)
|
||||
// ->pluck('total_quantity', 'time_unit')
|
||||
// ->toArray();
|
||||
|
||||
$query = \DB::table($table)
|
||||
->selectRaw("$groupBy AS time_unit, COUNT(*) AS total_quantity")
|
||||
->whereBetween("$table.$dateColumn", [$startDate, $endDate])
|
||||
->where("$table.$plantColumn", $selectedPlant)
|
||||
->where("$table.$lineColumn", $selectedLine)
|
||||
->where("$table.$orderColumn", $productionOrder);
|
||||
|
||||
// Apply status filter only for Non-FG lines
|
||||
if ($selectedStatus && $lineType != 'FG Line') {
|
||||
$query->where('success_status', $selectedStatus);
|
||||
}
|
||||
|
||||
$query = $query
|
||||
->groupByRaw($groupBy)
|
||||
->orderByRaw($groupBy)
|
||||
->pluck('total_quantity', 'time_unit')
|
||||
->toArray();
|
||||
|
||||
if ($activeFilter == 'this_month')
|
||||
{
|
||||
$weeksCount = ceil($endDate->day / 7);
|
||||
$allWeeks = array_fill(1, $weeksCount, 0);
|
||||
$data = array_replace($allWeeks, $query);
|
||||
|
||||
$labels = [];
|
||||
for ($i = 1; $i <= $weeksCount; $i++) {
|
||||
$weekStart = $startDate->copy()->addDays(($i - 1) * 7)->format('d M');
|
||||
$weekEnd = $startDate->copy()->addDays($i * 7 - 1)->min($endDate)->format('d M');
|
||||
$labels[] = "Week $i ($weekStart - $weekEnd)";
|
||||
}
|
||||
|
||||
$orderedData = array_values($data);
|
||||
}
|
||||
else if ($activeFilter == 'this_week')
|
||||
{
|
||||
$labels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
||||
$data = array_fill(0, 7, 0);
|
||||
foreach ($query as $dow => $count) {
|
||||
$data[$dow] = $count;
|
||||
}
|
||||
$orderedData = [
|
||||
$data[1] ?? 0, // Monday
|
||||
$data[2] ?? 0, // Tuesday
|
||||
$data[3] ?? 0, // Wednesday
|
||||
$data[4] ?? 0, // Thursday
|
||||
$data[5] ?? 0, // Friday
|
||||
$data[6] ?? 0, // Saturday
|
||||
$data[0] ?? 0, // Sunday
|
||||
];
|
||||
}
|
||||
// else
|
||||
// {
|
||||
// $allHours = array_fill(0, 24, 0);
|
||||
// $data = array_replace($allHours, $query);
|
||||
|
||||
// $shiftedKeys = array_merge(range(9, 23), range(0, 8));
|
||||
// $orderedData = array_map(fn($hour) => $data[$hour], $shiftedKeys);
|
||||
|
||||
// $labels = array_map(fn ($hour) => date("g A", strtotime("$hour:00")), $shiftedKeys);
|
||||
// }
|
||||
else
|
||||
{
|
||||
$allHours = array_fill(0, 24, 0);
|
||||
$data = array_replace($allHours, $query);
|
||||
|
||||
// Shift counts - move each hour's count to the next hour
|
||||
$shiftedData = [];
|
||||
foreach ($data as $hour => $count) {
|
||||
$nextHour = ($hour + 1) % 24;
|
||||
$shiftedData[$nextHour] = $count;
|
||||
}
|
||||
|
||||
// Order from 9AM to 8AM next day
|
||||
$shiftedKeys = array_merge(range(9, 23), range(0, 8));
|
||||
$orderedData = array_map(fn($hour) => $shiftedData[$hour] ?? 0, $shiftedKeys);
|
||||
|
||||
// Create labels
|
||||
$labels = array_map(function($hour) {
|
||||
return date("g A", strtotime("$hour:00"));
|
||||
}, $shiftedKeys);
|
||||
}
|
||||
|
||||
$orderedData = array_map(function ($value)
|
||||
{
|
||||
return ($value == 0 || is_null($value)) ? null : $value;
|
||||
}, $orderedData);
|
||||
|
||||
|
||||
return [
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => match ($activeFilter)
|
||||
{
|
||||
'this_week' => $lineType == 'FG Line'
|
||||
? "Daily Fg Production Order Count This Week"
|
||||
: "Daily Production Order Count This Week",
|
||||
'this_month' => $lineType == 'FG Line'
|
||||
? "Weekly Fg Production Order Count This Month"
|
||||
: "Weekly Production Order Count This Month",
|
||||
'yesterday' => $lineType == 'FG Line'
|
||||
? "Yesterday's Hourly Fg Order Count Production"
|
||||
: "Yesterday's Hourly Order Count Production",
|
||||
default => $lineType == 'FG Line'
|
||||
? "Today's Hourly Fg Production Order Count"
|
||||
: "Today's Hourly Production Order Count",
|
||||
},
|
||||
|
||||
'data' => $orderedData,
|
||||
'borderColor' => 'rgba(75, 192, 192, 1)',
|
||||
'backgroundColor' => 'rgba(75, 192, 192, 0.2)',
|
||||
'fill' => false,
|
||||
'tension' => 0.3,
|
||||
],
|
||||
],
|
||||
'labels' => $labels,
|
||||
];
|
||||
}
|
||||
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'bar';
|
||||
}
|
||||
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'plugins' => [
|
||||
'datalabels' => [
|
||||
'anchor' => 'start',
|
||||
'align' => 'start',
|
||||
'offset' => -15,
|
||||
'color' => '#000',
|
||||
'font' => [
|
||||
'weight' => 'bold',
|
||||
],
|
||||
'formatter' => Js::from("function(value) { return Number(value); }"),
|
||||
],
|
||||
],
|
||||
'scales' => [
|
||||
'y' => [
|
||||
'beginAtZero' => true,
|
||||
'ticks' => [
|
||||
'stepSize' => 0.5,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getFilters(): ?array
|
||||
{
|
||||
return [
|
||||
'today' => 'Today',
|
||||
'yesterday' => 'Yesterday',
|
||||
'this_week'=> 'This Week',
|
||||
'this_month'=> 'This Month',
|
||||
];
|
||||
}
|
||||
|
||||
public static function canView(): bool
|
||||
{
|
||||
// Only show on HourlyProduction page
|
||||
return request()->routeIs('filament.pages.production-order-count');
|
||||
}
|
||||
}
|
||||
92
app/Filament/Widgets/ProductionQuantityStat.php
Normal file
92
app/Filament/Widgets/ProductionQuantityStat.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Models\Line;
|
||||
use App\Models\ProductionQuantity;
|
||||
use App\Models\QualityValidation;
|
||||
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
|
||||
use Filament\Widgets\StatsOverviewWidget\Stat;
|
||||
|
||||
class ProductionQuantityStat extends BaseWidget
|
||||
{
|
||||
protected $listeners = ['filtersUpdated' => 'updateStats'];
|
||||
|
||||
public $selectedPlant;
|
||||
public $selectedLine;
|
||||
public $selectedFilter = 'Today';
|
||||
|
||||
public function updateStats($data=null)
|
||||
{
|
||||
if (!$data) {
|
||||
return;
|
||||
}
|
||||
$this->selectedPlant = $data['plant'] ?? null;
|
||||
$this->selectedLine = $data['line'] ?? null;
|
||||
//$this->selectedFilter = $data['filter'] ?? 'today';
|
||||
$this->selectedFilter = $data['filter'] ?? 'today';
|
||||
|
||||
$this->dispatch('$refresh');
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected function getStats(): array
|
||||
{
|
||||
if (!$this->selectedPlant || !$this->selectedLine) {
|
||||
return [
|
||||
Stat::make('Production Count', 0)
|
||||
->description('Select Plant & Line')
|
||||
->color('success'),
|
||||
];
|
||||
}
|
||||
|
||||
$line = Line::find($this->selectedLine);
|
||||
|
||||
if (!$line) {
|
||||
return [
|
||||
Stat::make('Production Count', 0)
|
||||
->description('Line not found')
|
||||
->color('success'),
|
||||
];
|
||||
}
|
||||
|
||||
$start = now()->setTime(8, 0, 0);
|
||||
$end = now()->copy()->addDay()->setTime(8, 0, 0);
|
||||
|
||||
if ($this->selectedFilter == 'yesterday') {
|
||||
$start = now()->subDay()->setTime(8, 0, 0);
|
||||
$end = now()->setTime(8, 0, 0);
|
||||
} elseif ($this->selectedFilter == 'this_week') {
|
||||
$start = now()->startOfWeek()->setTime(8, 0, 0);
|
||||
$end = now()->endOfWeek()->addDay()->setTime(8, 0, 0);
|
||||
} elseif ($this->selectedFilter == 'this_month') {
|
||||
$start = now()->startOfMonth()->setTime(8, 0, 0);
|
||||
$end = now()->endOfMonth()->addDay()->setTime(8, 0, 0);
|
||||
}
|
||||
|
||||
if ($line->type === 'FG Line') {
|
||||
$count = QualityValidation::where('line_id', $line->id)
|
||||
->whereBetween('created_at', [$start, $end])
|
||||
->count();
|
||||
|
||||
return [
|
||||
Stat::make('FG Production Count', $count)
|
||||
->description("FG production for this line")
|
||||
->color('success')
|
||||
->icon('heroicon-s-check-circle'),
|
||||
];
|
||||
} else {
|
||||
$count = ProductionQuantity::where('line_id', $line->id)
|
||||
->whereBetween('created_at', [$start, $end])
|
||||
->count();
|
||||
|
||||
return [
|
||||
Stat::make('Total Production Count', $count)
|
||||
->description("Total production for this line")
|
||||
->color('primary')
|
||||
->icon('heroicon-s-cube'),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
553
app/Filament/Widgets/TrendChartAnalysis.php
Normal file
553
app/Filament/Widgets/TrendChartAnalysis.php
Normal file
@@ -0,0 +1,553 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Models\MfmReading;
|
||||
use Carbon\Carbon;
|
||||
use Filament\Widgets\ChartWidget;
|
||||
use Filament\Support\RawJs;
|
||||
|
||||
class TrendChartAnalysis extends ChartWidget
|
||||
{
|
||||
protected static ?string $heading = 'Trend Chart Analysis';
|
||||
|
||||
|
||||
protected static ?string $maxHeight = '420px';
|
||||
|
||||
|
||||
protected int|string|array $columnSpan = 12;
|
||||
|
||||
// Add a property to receive filters from parent or externally
|
||||
|
||||
protected function getData(): array
|
||||
{
|
||||
$fromDatetime = session('from_datetime');
|
||||
$toDatetime = session('to_datetime');
|
||||
$selectedPlant = session('selected_plant');
|
||||
$meterId = session('selected_meter');
|
||||
$parameter = session('parameter');
|
||||
|
||||
if (empty($fromDatetime) || empty($toDatetime) || empty($selectedPlant) || empty($meterId) || empty($parameter)) {
|
||||
return [
|
||||
'labels' => [],
|
||||
'datasets' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$fromDateTime = Carbon::parse($fromDatetime);
|
||||
$toDateTime = Carbon::parse($toDatetime);
|
||||
|
||||
if ($fromDateTime->gt($toDateTime) || $fromDateTime->gt(now()) || $toDateTime->gt(now())) {
|
||||
return [
|
||||
'labels' => [],
|
||||
'datasets' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$durationHours = $fromDateTime->diffInHours($toDateTime);
|
||||
//dd($durationHours);
|
||||
if ($durationHours < 1 || $durationHours > 24) {
|
||||
return [
|
||||
'labels' => [],
|
||||
'datasets' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$intervalCount = $durationHours > 12 ? 12 : 10;
|
||||
$intervalMinutes = $durationHours > 12 ? 120 : floor(($durationHours * 60) / $intervalCount);
|
||||
|
||||
$labels = [];
|
||||
|
||||
if ($parameter == 'Phase Voltage') {
|
||||
|
||||
// Helper function to get min, max, avg for a column with PostgreSQL casting
|
||||
$dataAggregation = function (string $column) use ($fromDateTime, $toDateTime, $meterId) {
|
||||
$min = MfmReading::whereBetween('created_at', [$fromDateTime, $toDateTime])
|
||||
->where('mfm_meter_id', $meterId)
|
||||
->min(\Illuminate\Support\Facades\DB::raw("{$column}::double precision"));
|
||||
$max = MfmReading::whereBetween('created_at', [$fromDateTime, $toDateTime])
|
||||
->where('mfm_meter_id', $meterId)
|
||||
->max(\Illuminate\Support\Facades\DB::raw("{$column}::double precision"));
|
||||
$avg = MfmReading::whereBetween('created_at', [$fromDateTime, $toDateTime])
|
||||
->where('mfm_meter_id', $meterId)
|
||||
->avg(\Illuminate\Support\Facades\DB::raw("{$column}::double precision"));
|
||||
|
||||
return [
|
||||
'min' => round($min ?? 0, 2),
|
||||
'max' => round($max ?? 0, 2),
|
||||
'avg' => round($avg ?? 0, 2),
|
||||
];
|
||||
};
|
||||
|
||||
// Get aggregated data for all relevant columns
|
||||
$voltageRYSummary = $dataAggregation('voltage_ry');
|
||||
$voltageYBSummary = $dataAggregation('voltage_yb');
|
||||
$voltageBRSummary = $dataAggregation('voltage_br');
|
||||
$frequencySummary = $dataAggregation('frequency');
|
||||
|
||||
// Labels for each bar on X-axis (min, max, avg per column)
|
||||
$labels = [
|
||||
'Voltage RY Min', 'Voltage RY Max', 'Voltage RY Avg',
|
||||
'Voltage YB Min', 'Voltage YB Max', 'Voltage YB Avg',
|
||||
'Voltage BR Min', 'Voltage BR Max', 'Voltage BR Avg',
|
||||
'Frequency Min', 'Frequency Max', 'Frequency Avg',
|
||||
];
|
||||
|
||||
// Ordered data values matching labels above
|
||||
$dataValues = [
|
||||
$voltageRYSummary['min'], $voltageRYSummary['max'], $voltageRYSummary['avg'],
|
||||
$voltageYBSummary['min'], $voltageYBSummary['max'], $voltageYBSummary['avg'],
|
||||
$voltageBRSummary['min'], $voltageBRSummary['max'], $voltageBRSummary['avg'],
|
||||
$frequencySummary['min'], $frequencySummary['max'], $frequencySummary['avg'],
|
||||
];
|
||||
|
||||
// Colors by aggregation type: Min - yellow, Max - green, Avg - blue
|
||||
$aggregationColors = [
|
||||
'rgba(204, 163, 0, 0.8)', // Darker yellow (Min)
|
||||
'rgba(30, 120, 120, 0.8)', // Darker teal/green (Max)
|
||||
'rgba(30, 90, 140, 0.8)', // Darker blue (Avg)
|
||||
];
|
||||
|
||||
// Each set of 3 bars per column repeats the colors: min, max, avg
|
||||
$backgroundColorArray = [];
|
||||
foreach (range(1, 4) as $group) { // 4 column groups
|
||||
foreach ($aggregationColors as $color) {
|
||||
$backgroundColorArray[] = $color;
|
||||
}
|
||||
}
|
||||
|
||||
// Construct chart data structure
|
||||
return [
|
||||
'labels' => $labels,
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Phase Voltage & Frequency Stats',
|
||||
// Bar values array: 12 bars total (4 groups × 3 stats each)
|
||||
'data' => $dataValues,
|
||||
'backgroundColor' => $backgroundColorArray,
|
||||
'borderColor' => array_map(fn ($clr) => str_replace('0.6', '1', $clr), $backgroundColorArray),
|
||||
'borderWidth' => 1,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
else if ($parameter == 'Line Voltage') {
|
||||
|
||||
// Helper function to get min, max, avg for a column with PostgreSQL casting
|
||||
$dataAggregation = function (string $column) use ($fromDateTime, $toDateTime, $meterId) {
|
||||
$min = MfmReading::whereBetween('created_at', [$fromDateTime, $toDateTime])
|
||||
->where('mfm_meter_id', $meterId)
|
||||
->min(\Illuminate\Support\Facades\DB::raw("{$column}::double precision"));
|
||||
$max = MfmReading::whereBetween('created_at', [$fromDateTime, $toDateTime])
|
||||
->where('mfm_meter_id', $meterId)
|
||||
->max(\Illuminate\Support\Facades\DB::raw("{$column}::double precision"));
|
||||
$avg = MfmReading::whereBetween('created_at', [$fromDateTime, $toDateTime])
|
||||
->where('mfm_meter_id', $meterId)
|
||||
->avg(\Illuminate\Support\Facades\DB::raw("{$column}::double precision"));
|
||||
|
||||
return [
|
||||
'min' => round($min ?? 0, 2),
|
||||
'max' => round($max ?? 0, 2),
|
||||
'avg' => round($avg ?? 0, 2),
|
||||
];
|
||||
};
|
||||
|
||||
// Get aggregated data for all relevant columns
|
||||
$voltageRNSummary = $dataAggregation('voltage_r_n');
|
||||
$voltageYNSummary = $dataAggregation('voltage_y_n');
|
||||
$voltageBNSummary = $dataAggregation('voltage_b_n');
|
||||
$frequencySummary = $dataAggregation('frequency');
|
||||
|
||||
// Labels for each bar on X-axis (min, max, avg per column)
|
||||
$labels = [
|
||||
'Voltage RN Min', 'Voltage RN Max', 'Voltage RN Avg',
|
||||
'Voltage YN Min', 'Voltage YN Max', 'Voltage YN Avg',
|
||||
'Voltage BN Min', 'Voltage BN Max', 'Voltage BN Avg',
|
||||
'Frequency Min', 'Frequency Max', 'Frequency Avg',
|
||||
];
|
||||
|
||||
// Ordered data values matching labels above
|
||||
$dataValues = [
|
||||
$voltageRNSummary['min'], $voltageRNSummary['max'], $voltageRNSummary['avg'],
|
||||
$voltageYNSummary['min'], $voltageYNSummary['max'], $voltageYNSummary['avg'],
|
||||
$voltageBNSummary['min'], $voltageBNSummary['max'], $voltageBNSummary['avg'],
|
||||
$frequencySummary['min'], $frequencySummary['max'], $frequencySummary['avg'],
|
||||
];
|
||||
|
||||
// Colors by aggregation type: Min - yellow, Max - green, Avg - blue
|
||||
$aggregationColors = [
|
||||
'rgba(204, 163, 0, 0.8)', // Darker yellow (Min)
|
||||
'rgba(30, 120, 120, 0.8)', // Darker teal/green (Max)
|
||||
'rgba(30, 90, 140, 0.8)', // Darker blue (Avg)
|
||||
];
|
||||
|
||||
// Each set of 3 bars per column repeats the colors: min, max, avg
|
||||
$backgroundColorArray = [];
|
||||
foreach (range(1, 4) as $group) { // 4 column groups
|
||||
foreach ($aggregationColors as $color) {
|
||||
$backgroundColorArray[] = $color;
|
||||
}
|
||||
}
|
||||
|
||||
// Construct chart data structure
|
||||
return [
|
||||
'labels' => $labels,
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Phase Voltage & Frequency Stats',
|
||||
// Bar values array: 12 bars total (4 groups × 3 stats each)
|
||||
'data' => $dataValues,
|
||||
'backgroundColor' => $backgroundColorArray,
|
||||
'borderColor' => array_map(fn ($clr) => str_replace('0.6', '1', $clr), $backgroundColorArray),
|
||||
'borderWidth' => 1,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
else if ($parameter == 'Current') {
|
||||
|
||||
// Helper function to get min, max, avg for a column with PostgreSQL casting
|
||||
$dataAggregation = function (string $column) use ($fromDateTime, $toDateTime, $meterId) {
|
||||
$min = MfmReading::whereBetween('created_at', [$fromDateTime, $toDateTime])
|
||||
->where('mfm_meter_id', $meterId)
|
||||
->min(\Illuminate\Support\Facades\DB::raw("{$column}::double precision"));
|
||||
$max = MfmReading::whereBetween('created_at', [$fromDateTime, $toDateTime])
|
||||
->where('mfm_meter_id', $meterId)
|
||||
->max(\Illuminate\Support\Facades\DB::raw("{$column}::double precision"));
|
||||
$avg = MfmReading::whereBetween('created_at', [$fromDateTime, $toDateTime])
|
||||
->where('mfm_meter_id', $meterId)
|
||||
->avg(\Illuminate\Support\Facades\DB::raw("{$column}::double precision"));
|
||||
|
||||
return [
|
||||
'min' => round($min ?? 0, 2),
|
||||
'max' => round($max ?? 0, 2),
|
||||
'avg' => round($avg ?? 0, 2),
|
||||
];
|
||||
};
|
||||
|
||||
// Get aggregated data for all relevant columns
|
||||
$curR = $dataAggregation('current_r');
|
||||
$curY = $dataAggregation('current_y');
|
||||
$curB = $dataAggregation('current_b');
|
||||
$curN = $dataAggregation('current_n');
|
||||
|
||||
// Labels for each bar on X-axis (min, max, avg per column)
|
||||
$labels = [
|
||||
'Current R Min', 'Current R Max', 'Current R Avg',
|
||||
'Current Y Min', 'Current Y Max', 'Current Y Avg',
|
||||
'Current B Min', 'Current B Max', 'Current B Avg',
|
||||
'Current N Min', 'Current N Max', 'Current N Avg',
|
||||
];
|
||||
|
||||
// Ordered data values matching labels above
|
||||
$dataValues = [
|
||||
$curR['min'], $curR['max'], $curR['avg'],
|
||||
$curY['min'], $curY['max'], $curY['avg'],
|
||||
$curB['min'], $curB['max'], $curB['avg'],
|
||||
$curN['min'], $curN['max'], $curN['avg'],
|
||||
];
|
||||
|
||||
// Colors by aggregation type: Min - yellow, Max - green, Avg - blue
|
||||
$aggregationColors = [
|
||||
'rgba(204, 163, 0, 0.8)', // Darker yellow (Min)
|
||||
'rgba(30, 120, 120, 0.8)', // Darker teal/green (Max)
|
||||
'rgba(30, 90, 140, 0.8)', // Darker blue (Avg)
|
||||
];
|
||||
|
||||
// Each set of 3 bars per column repeats the colors: min, max, avg
|
||||
$backgroundColorArray = [];
|
||||
foreach (range(1, 4) as $group) { // 4 column groups
|
||||
foreach ($aggregationColors as $color) {
|
||||
$backgroundColorArray[] = $color;
|
||||
}
|
||||
}
|
||||
|
||||
// Construct chart data structure
|
||||
return [
|
||||
'labels' => $labels,
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Phase Voltage & Frequency Stats',
|
||||
// Bar values array: 12 bars total (4 groups × 3 stats each)
|
||||
'data' => $dataValues,
|
||||
'backgroundColor' => $backgroundColorArray,
|
||||
'borderColor' => array_map(fn ($clr) => str_replace('0.6', '1', $clr), $backgroundColorArray),
|
||||
'borderWidth' => 1,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
else if ($parameter == 'Active Power') {
|
||||
|
||||
// Helper function to get min, max, avg for a column with PostgreSQL casting
|
||||
$dataAggregation = function (string $column) use ($fromDateTime, $toDateTime, $meterId) {
|
||||
$min = MfmReading::whereBetween('created_at', [$fromDateTime, $toDateTime])
|
||||
->where('mfm_meter_id', $meterId)
|
||||
->min(\Illuminate\Support\Facades\DB::raw("{$column}::double precision"));
|
||||
$max = MfmReading::whereBetween('created_at', [$fromDateTime, $toDateTime])
|
||||
->where('mfm_meter_id', $meterId)
|
||||
->max(\Illuminate\Support\Facades\DB::raw("{$column}::double precision"));
|
||||
$avg = MfmReading::whereBetween('created_at', [$fromDateTime, $toDateTime])
|
||||
->where('mfm_meter_id', $meterId)
|
||||
->avg(\Illuminate\Support\Facades\DB::raw("{$column}::double precision"));
|
||||
|
||||
return [
|
||||
'min' => round($min ?? 0, 2),
|
||||
'max' => round($max ?? 0, 2),
|
||||
'avg' => round($avg ?? 0, 2),
|
||||
];
|
||||
};
|
||||
|
||||
// Get aggregated data for all relevant columns
|
||||
$activePowR = $dataAggregation('active_power_r');
|
||||
$activePowY = $dataAggregation('active_power_y');
|
||||
$activePowB = $dataAggregation('active_power_b');
|
||||
$activePowTot = $dataAggregation('active_power_total');
|
||||
|
||||
// Labels for each bar on X-axis (min, max, avg per column)
|
||||
$labels = [
|
||||
'ActivePow R Min', 'ActivePow R Max', 'ActivePow R Avg',
|
||||
'ActivePow Y Min', 'ActivePow Y Max', 'ActivePow Y Avg',
|
||||
'ActivePow B Min', 'ActivePow B Max', 'ActivePow B Avg',
|
||||
'ActivePow Tot Min', 'ActivePow Tot Max', 'ActivePow Tot Avg',
|
||||
];
|
||||
|
||||
// Ordered data values matching labels above
|
||||
$dataValues = [
|
||||
$activePowR['min'], $activePowR['max'], $activePowR['avg'],
|
||||
$activePowY['min'], $activePowY['max'], $activePowY['avg'],
|
||||
$activePowB['min'], $activePowB['max'], $activePowB['avg'],
|
||||
$activePowTot['min'], $activePowTot['max'], $activePowTot['avg'],
|
||||
];
|
||||
|
||||
// Colors by aggregation type: Min - yellow, Max - green, Avg - blue
|
||||
$aggregationColors = [
|
||||
'rgba(204, 163, 0, 0.8)', // Darker yellow (Min)
|
||||
'rgba(30, 120, 120, 0.8)', // Darker teal/green (Max)
|
||||
'rgba(30, 90, 140, 0.8)', // Darker blue (Avg)
|
||||
];
|
||||
|
||||
// Each set of 3 bars per column repeats the colors: min, max, avg
|
||||
$backgroundColorArray = [];
|
||||
foreach (range(1, 4) as $group) { // 4 column groups
|
||||
foreach ($aggregationColors as $color) {
|
||||
$backgroundColorArray[] = $color;
|
||||
}
|
||||
}
|
||||
|
||||
// Construct chart data structure
|
||||
return [
|
||||
'labels' => $labels,
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Phase Voltage & Frequency Stats',
|
||||
// Bar values array: 12 bars total (4 groups × 3 stats each)
|
||||
'data' => $dataValues,
|
||||
'backgroundColor' => $backgroundColorArray,
|
||||
'borderColor' => array_map(fn ($clr) => str_replace('0.6', '1', $clr), $backgroundColorArray),
|
||||
'borderWidth' => 1,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
else if ($parameter == 'Power Factor') {
|
||||
|
||||
// Helper function to get min, max, avg for a column with PostgreSQL casting
|
||||
$dataAggregation = function (string $column) use ($fromDateTime, $toDateTime, $meterId) {
|
||||
$min = MfmReading::whereBetween('created_at', [$fromDateTime, $toDateTime])
|
||||
->where('mfm_meter_id', $meterId)
|
||||
->min(\Illuminate\Support\Facades\DB::raw("{$column}::double precision"));
|
||||
$max = MfmReading::whereBetween('created_at', [$fromDateTime, $toDateTime])
|
||||
->where('mfm_meter_id', $meterId)
|
||||
->max(\Illuminate\Support\Facades\DB::raw("{$column}::double precision"));
|
||||
$avg = MfmReading::whereBetween('created_at', [$fromDateTime, $toDateTime])
|
||||
->where('mfm_meter_id', $meterId)
|
||||
->avg(\Illuminate\Support\Facades\DB::raw("{$column}::double precision"));
|
||||
|
||||
return [
|
||||
'min' => round($min ?? 0, 2),
|
||||
'max' => round($max ?? 0, 2),
|
||||
'avg' => round($avg ?? 0, 2),
|
||||
];
|
||||
};
|
||||
|
||||
// Get aggregated data for all relevant columns
|
||||
$powFacR = $dataAggregation('power_factor_r');
|
||||
$powFacY = $dataAggregation('power_factor_y');
|
||||
$powFacB = $dataAggregation('power_factor_b');
|
||||
$powFacTot = $dataAggregation('power_factor_total');
|
||||
|
||||
// Labels for each bar on X-axis (min, max, avg per column)
|
||||
$labels = [
|
||||
'PowerFac R Min', 'PowerFac R Max', 'PowerFac R Avg',
|
||||
'PowerFac Y Min', 'PowerFac Y Max', 'PowerFac Y Avg',
|
||||
'PowerFac B Min', 'PowerFac B Max', 'PowerFac B Avg',
|
||||
'PowerFac Tot Min', 'PowerFac Tot Max', 'PowerFac Tot Avg',
|
||||
];
|
||||
|
||||
// Ordered data values matching labels above
|
||||
$dataValues = [
|
||||
$powFacR['min'], $powFacR['max'], $powFacR['avg'],
|
||||
$powFacY['min'], $powFacY['max'], $powFacY['avg'],
|
||||
$powFacB['min'], $powFacB['max'], $powFacB['avg'],
|
||||
$powFacTot['min'], $powFacTot['max'], $powFacTot['avg'],
|
||||
];
|
||||
|
||||
// Colors by aggregation type: Min - yellow, Max - green, Avg - blue
|
||||
$aggregationColors = [
|
||||
'rgba(204, 163, 0, 0.8)', // Darker yellow (Min)
|
||||
'rgba(30, 120, 120, 0.8)', // Darker teal/green (Max)
|
||||
'rgba(30, 90, 140, 0.8)', // Darker blue (Avg)
|
||||
];
|
||||
|
||||
// Each set of 3 bars per column repeats the colors: min, max, avg
|
||||
$backgroundColorArray = [];
|
||||
foreach (range(1, 4) as $group) { // 4 column groups
|
||||
foreach ($aggregationColors as $color) {
|
||||
$backgroundColorArray[] = $color;
|
||||
}
|
||||
}
|
||||
|
||||
// Construct chart data structure
|
||||
return [
|
||||
'labels' => $labels,
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Phase Voltage & Frequency Stats',
|
||||
// Bar values array: 12 bars total (4 groups × 3 stats each)
|
||||
'data' => $dataValues,
|
||||
'backgroundColor' => $backgroundColorArray,
|
||||
'borderColor' => array_map(fn ($clr) => str_replace('0.6', '1', $clr), $backgroundColorArray),
|
||||
'borderWidth' => 1,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
else if ($parameter == 'Units') {
|
||||
|
||||
// Helper function to get min, max, avg for a column with PostgreSQL casting
|
||||
$dataAggregation = function (string $column) use ($fromDateTime, $toDateTime, $meterId) {
|
||||
$min = MfmReading::whereBetween('created_at', [$fromDateTime, $toDateTime])
|
||||
->where('mfm_meter_id', $meterId)
|
||||
->min(\Illuminate\Support\Facades\DB::raw("{$column}::double precision"));
|
||||
$max = MfmReading::whereBetween('created_at', [$fromDateTime, $toDateTime])
|
||||
->where('mfm_meter_id', $meterId)
|
||||
->max(\Illuminate\Support\Facades\DB::raw("{$column}::double precision"));
|
||||
$avg = MfmReading::whereBetween('created_at', [$fromDateTime, $toDateTime])
|
||||
->where('mfm_meter_id', $meterId)
|
||||
->avg(\Illuminate\Support\Facades\DB::raw("{$column}::double precision"));
|
||||
|
||||
return [
|
||||
'min' => round($min ?? 0, 2),
|
||||
'max' => round($max ?? 0, 2),
|
||||
'avg' => round($avg ?? 0, 2),
|
||||
];
|
||||
};
|
||||
|
||||
// Get aggregated data for all relevant columns
|
||||
$appEneRec = $dataAggregation('apparent_energy_received');
|
||||
$reaEneRec = $dataAggregation('reactive_energy_received');
|
||||
$actEneRec = $dataAggregation('active_energy_received');
|
||||
|
||||
|
||||
// Labels for each bar on X-axis (min, max, avg per column)
|
||||
$labels = [
|
||||
'AppEneRec Min', 'AppEneRec Max', 'AppEneRec Avg',
|
||||
'ReaEneRec Min', 'ReaEneRec Max', 'ReaEneRec Avg',
|
||||
'ActEneRec Min', 'ActEneRec Max', 'ActEneRec Avg',
|
||||
];
|
||||
|
||||
// Ordered data values matching labels above
|
||||
$dataValues = [
|
||||
$appEneRec['min'], $appEneRec['max'], $appEneRec['avg'],
|
||||
$reaEneRec['min'], $reaEneRec['max'], $reaEneRec['avg'],
|
||||
$actEneRec['min'], $actEneRec['max'], $actEneRec['avg'],
|
||||
];
|
||||
|
||||
// Colors by aggregation type: Min - yellow, Max - green, Avg - blue
|
||||
$aggregationColors = [
|
||||
'rgba(204, 163, 0, 0.8)', // Darker yellow (Min)
|
||||
'rgba(30, 120, 120, 0.8)', // Darker teal/green (Max)
|
||||
'rgba(30, 90, 140, 0.8)', // Darker blue (Avg)
|
||||
];
|
||||
|
||||
// Each set of 3 bars per column repeats the colors: min, max, avg
|
||||
$backgroundColorArray = [];
|
||||
foreach (range(1, 4) as $group) { // 4 column groups
|
||||
foreach ($aggregationColors as $color) {
|
||||
$backgroundColorArray[] = $color;
|
||||
}
|
||||
}
|
||||
|
||||
// Construct chart data structure
|
||||
return [
|
||||
'labels' => $labels,
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => 'Phase Voltage & Frequency Stats',
|
||||
// Bar values array: 12 bars total (4 groups × 3 stats each)
|
||||
'data' => $dataValues,
|
||||
'backgroundColor' => $backgroundColorArray,
|
||||
'borderColor' => array_map(fn ($clr) => str_replace('0.6', '1', $clr), $backgroundColorArray),
|
||||
'borderWidth' => 1,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
else
|
||||
{
|
||||
$data = [];
|
||||
$labels = [];
|
||||
$currentStart = $fromDateTime->copy();
|
||||
return [
|
||||
'labels' => $labels,
|
||||
'datasets' => [
|
||||
[
|
||||
'label' => ucfirst(str_replace('_', ' ', $parameter)),
|
||||
'data' => $data,
|
||||
'backgroundColor' => 'rgba(75, 192, 192, 0.2)',
|
||||
'borderColor' => 'rgba(75, 192, 192, 1)',
|
||||
'fill' => false,
|
||||
'tension' => 0.3,
|
||||
'pointRadius' => 5,
|
||||
'pointHoverRadius' => 7,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
protected function getOptions(): array
|
||||
{
|
||||
return [
|
||||
'plugins' => [
|
||||
'datalabels' => [
|
||||
'anchor' => 'start',
|
||||
'align' => 'start',
|
||||
'offset' => -15,
|
||||
'color' => '#000',
|
||||
'font' => [
|
||||
'weight' => 'bold',
|
||||
],
|
||||
'formatter' => RawJs::make('function(value) {
|
||||
return value;
|
||||
}'),
|
||||
],
|
||||
],
|
||||
'scales' => [
|
||||
'y' => [
|
||||
'beginAtZero' => true,
|
||||
'ticks' => [
|
||||
'stepSize' => 1,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'bar';
|
||||
}
|
||||
}
|
||||
206
app/Filament/Widgets/TrendLineChart.php
Normal file
206
app/Filament/Widgets/TrendLineChart.php
Normal file
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Widgets;
|
||||
|
||||
use App\Models\MfmReading;
|
||||
use Carbon\Carbon;
|
||||
use Filament\Widgets\ChartWidget;
|
||||
|
||||
class TrendLineChart extends ChartWidget
|
||||
{
|
||||
protected static ?string $heading = 'Trend Line Analysis';
|
||||
|
||||
protected static ?string $maxHeight = '420px';
|
||||
|
||||
protected int|string|array $columnSpan = 12;
|
||||
|
||||
protected function getData(): array
|
||||
{
|
||||
$fromDatetime = session('from_datetime');
|
||||
$toDatetime = session('to_datetime');
|
||||
$selectedPlant = session('selected_plant');
|
||||
$meterId = session('selected_meter');
|
||||
$parameter = session('parameter');
|
||||
|
||||
if (empty($fromDatetime) || empty($toDatetime) || empty($selectedPlant) || empty($meterId) || empty($parameter)) {
|
||||
return [
|
||||
'labels' => [],
|
||||
'datasets' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$fromDateTime = Carbon::parse($fromDatetime);
|
||||
$toDateTime = Carbon::parse($toDatetime);
|
||||
|
||||
if ($fromDateTime->gt($toDateTime) || $fromDateTime->gt(now()) || $toDateTime->gt(now())) {
|
||||
return [
|
||||
'labels' => [],
|
||||
'datasets' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$durationHours = $fromDateTime->diffInHours($toDateTime);
|
||||
if ($durationHours < 1 || $durationHours > 24) {
|
||||
return [
|
||||
'labels' => [],
|
||||
'datasets' => [],
|
||||
];
|
||||
}
|
||||
|
||||
$intervalCount = $durationHours > 12 ? 12 : 10;
|
||||
$intervalMinutes = $durationHours > 12 ? 120 : floor(($durationHours * 60) / $intervalCount);
|
||||
|
||||
$labels = [];
|
||||
$columnMap = [];
|
||||
$datasetColors = [];
|
||||
$dataSeries = [];
|
||||
|
||||
// Determine columns and labels based on selected parameter
|
||||
switch ($parameter) {
|
||||
case 'Phase Voltage':
|
||||
$columnMap = [
|
||||
'voltage_ry' => 'Voltage RY Max',
|
||||
'voltage_yb' => 'Voltage YB Max',
|
||||
'voltage_br' => 'Voltage BR Max',
|
||||
'frequency' => 'Frequency Max',
|
||||
];
|
||||
$datasetColors = [
|
||||
'voltage_ry' => 'rgba(255, 99, 132, 1)', // red
|
||||
'voltage_yb' => 'rgba(54, 162, 235, 1)', // blue
|
||||
'voltage_br' => 'rgba(255, 206, 86, 1)', // yellow
|
||||
'frequency' => 'rgba(75, 192, 192, 1)', // teal
|
||||
];
|
||||
break;
|
||||
|
||||
case 'Line Voltage':
|
||||
$columnMap = [
|
||||
'voltage_r_n' => 'Voltage R-N Max',
|
||||
'voltage_y_n' => 'Voltage Y-N Max',
|
||||
'voltage_b_n' => 'Voltage B-N Max',
|
||||
'frequency' => 'Frequency Max',
|
||||
];
|
||||
$datasetColors = [
|
||||
'voltage_r_n' => 'rgba(153, 102, 255, 1)', // purple
|
||||
'voltage_y_n' => 'rgba(255, 159, 64, 1)', // orange
|
||||
'voltage_b_n' => 'rgba(0, 200, 83, 1)', // green
|
||||
'frequency' => 'rgba(75, 192, 192, 1)', // teal
|
||||
];
|
||||
break;
|
||||
case 'Current':
|
||||
$columnMap = [
|
||||
'current_r' => 'Current R Max',
|
||||
'current_y' => 'Current Y Max',
|
||||
'current_b' => 'Current B Max',
|
||||
'current_n' => 'Current N Max ',
|
||||
];
|
||||
$datasetColors = [
|
||||
'current_r' => 'rgba(153, 102, 255, 1)', // purple
|
||||
'current_y' => 'rgba(255, 159, 64, 1)', // orange
|
||||
'current_b' => 'rgba(0, 200, 83, 1)', // green
|
||||
'current_n' => 'rgba(75, 192, 192, 1)', // teal
|
||||
];
|
||||
break;
|
||||
|
||||
case 'Active Power':
|
||||
$columnMap = [
|
||||
'active_power_r' => 'Active Pow R Max',
|
||||
'active_power_y' => 'Active Pow Y Max',
|
||||
'active_power_b' => 'Active Pow B Max',
|
||||
'active_power_total' => 'Active Pow Tot Max ',
|
||||
];
|
||||
$datasetColors = [
|
||||
'active_power_r' => 'rgba(153, 102, 255, 1)', // purple
|
||||
'active_power_y' => 'rgba(255, 159, 64, 1)', // orange
|
||||
'active_power_b' => 'rgba(0, 200, 83, 1)', // green
|
||||
'active_power_total' => 'rgba(75, 192, 192, 1)', // teal
|
||||
];
|
||||
break;
|
||||
|
||||
case 'Power Factor':
|
||||
$columnMap = [
|
||||
'power_factor_r' => 'Power Fac R Max',
|
||||
'power_factor_y' => 'Power Fac Y Max',
|
||||
'power_factor_b' => 'Power Fac B Max',
|
||||
'power_factor_total' => 'Power Fac Tot Max ',
|
||||
];
|
||||
$datasetColors = [
|
||||
'power_factor_r' => 'rgba(153, 102, 255, 1)', // purple
|
||||
'power_factor_y' => 'rgba(255, 159, 64, 1)', // orange
|
||||
'power_factor_b' => 'rgba(0, 200, 83, 1)', // green
|
||||
'power_factor_total' => 'rgba(75, 192, 192, 1)', // teal
|
||||
];
|
||||
break;
|
||||
|
||||
case 'Units':
|
||||
$columnMap = [
|
||||
'apparent_energy_received' => 'AppEneRec Max',
|
||||
'reactive_energy_received' => 'ReacEneRec Max',
|
||||
'active_energy_received' => 'ActiveEneRec Max',
|
||||
];
|
||||
$datasetColors = [
|
||||
'apparent_energy_received' => 'rgba(153, 102, 255, 1)', // purple
|
||||
'reactive_energy_received' => 'rgba(255, 159, 64, 1)', // orange
|
||||
'active_energy_received' => 'rgba(0, 200, 83, 1)', // green
|
||||
];
|
||||
break;
|
||||
|
||||
default:
|
||||
return [
|
||||
'labels' => [],
|
||||
'datasets' => [],
|
||||
];
|
||||
}
|
||||
|
||||
// Initialize empty arrays for each data series
|
||||
foreach ($columnMap as $column => $label) {
|
||||
$dataSeries[$column] = [];
|
||||
}
|
||||
|
||||
$current = $fromDateTime->copy();
|
||||
while ($current < $toDateTime) {
|
||||
$next = $current->copy()->addMinutes($intervalMinutes);
|
||||
|
||||
$selectParts = [];
|
||||
foreach ($columnMap as $column => $label) {
|
||||
$selectParts[] = "MAX({$column}::double precision) as {$column}";
|
||||
}
|
||||
|
||||
$readings = MfmReading::where('mfm_meter_id', $meterId)
|
||||
->whereBetween('created_at', [$current, $next])
|
||||
->selectRaw(implode(', ', $selectParts))
|
||||
->first();
|
||||
|
||||
$labels[] = $current->format('H:i');
|
||||
|
||||
foreach ($columnMap as $column => $label) {
|
||||
$dataSeries[$column][] = round($readings->{$column} ?? 0, 2);
|
||||
}
|
||||
|
||||
$current = $next;
|
||||
}
|
||||
|
||||
// Construct dataset array
|
||||
$datasets = [];
|
||||
foreach ($columnMap as $column => $label) {
|
||||
$datasets[] = [
|
||||
'label' => $label,
|
||||
'data' => $dataSeries[$column],
|
||||
'borderColor' => $datasetColors[$column],
|
||||
'backgroundColor' => str_replace('1)', '0.2)', $datasetColors[$column]),
|
||||
'fill' => false,
|
||||
'tension' => 0.1,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'labels' => $labels,
|
||||
'datasets' => $datasets,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
protected function getType(): string
|
||||
{
|
||||
return 'line';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user