TKK_E32230220/app/Http/Controllers/DashboardController.php

428 lines
13 KiB
PHP

<?php
namespace App\Http\Controllers;
use App\Models\SortingHistory;
use App\Models\SortingSession;
use App\Models\FruitImage;
use App\Services\TelegramService;
use Illuminate\Http\Request;
use Carbon\Carbon;
class DashboardController extends Controller
{
public function index()
{
return view("dashboard");
}
public function history()
{
return view("history");
}
public function gallery()
{
return view("gallery");
}
public function latestData(Request $request)
{
$activeSession = SortingSession::active()->first();
$query = SortingHistory::latest("classification_date");
$this->applyFilters(
$query,
$request,
"classification_date",
$activeSession,
);
$latest = (clone $query)->first();
$history = (clone $query)->limit(200)->get();
// Hitung total kalkulasi dari query yang terfilter
$calcQuery = clone $query;
$totals = [
"total_raw_banana_weight" => (float) $calcQuery->sum("raw_banana_weight"),
"total_ripe_banana_weight" => (float) $calcQuery->sum("ripe_banana_weight"),
"total_raw_orange_weight" => (float) $calcQuery->sum("raw_orange_weight"),
"total_ripe_orange_weight" => (float) $calcQuery->sum("ripe_orange_weight"),
"total_records" => (int) $calcQuery->count(),
];
// Daftar sesi yang sudah selesai untuk dropdown di frontend
$sessions = SortingSession::where("status", "completed")
->latest("ended_at")
->limit(20)
->get()
->map(function ($s) {
return [
"id" => (string) $s->_id,
"started_at" => $s->started_at,
"ended_at" => $s->ended_at,
"total_records" => $s->total_records,
];
});
return response()->json([
"latest" => $latest,
"history" => $history,
"session" => $activeSession
? [
"id" => (string) $activeSession->_id,
"status" => $activeSession->status,
"started_at" => $activeSession->started_at,
]
: null,
"sessions" => $sessions,
"totals" => $totals,
]);
}
/**
* Mulai sesi sortir baru
* POST /api/session/start
*/
public function startSession()
{
// Cek apakah sudah ada sesi aktif
$existing = SortingSession::active()->first();
if ($existing) {
return response()->json(
[
"success" => false,
"message" => "Sudah ada sesi sortir yang aktif.",
"session" => $existing,
],
409,
);
}
$session = SortingSession::create([
"status" => "active",
"started_at" => now(),
"ended_at" => null,
"total_raw_banana_weight" => 0,
"total_ripe_banana_weight" => 0,
"total_raw_orange_weight" => 0,
"total_ripe_orange_weight" => 0,
"total_records" => 0,
]);
return response()->json([
"success" => true,
"message" => "Sesi sortir dimulai.",
"session" => [
"id" => (string) $session->_id,
"status" => $session->status,
"started_at" => $session->started_at,
],
]);
}
/**
* Stop sesi sortir, kalkulasi hasil, kirim Telegram
* POST /api/session/stop
*/
public function stopSession()
{
$session = SortingSession::active()->first();
if (!$session) {
return response()->json(
[
"success" => false,
"message" => "Tidak ada sesi sortir yang aktif.",
],
404,
);
}
// Kalkulasi total dari semua data yang masuk selama sesi ini
$sessionId = (string) $session->_id;
$records = SortingHistory::where("session_id", $sessionId)->get();
$totals = [
"total_raw_banana_weight" => $records->sum("raw_banana_weight"),
"total_ripe_banana_weight" => $records->sum("ripe_banana_weight"),
"total_raw_orange_weight" => $records->sum("raw_orange_weight"),
"total_ripe_orange_weight" => $records->sum("ripe_orange_weight"),
"total_records" => $records->count(),
];
// Update sesi dengan hasil kalkulasi
$session->update(
array_merge($totals, [
"status" => "completed",
"ended_at" => now(),
]),
);
// Refresh model setelah update
$session->refresh();
// Kirim hasil ke Telegram
try {
$telegramService = new TelegramService();
$telegramService->sendSessionResult($session);
} catch (\Exception $e) {
\Log::warning("Telegram alert gagal: " . $e->getMessage());
}
return response()->json([
"success" => true,
"message" => "Sesi sortir selesai. Hasil telah dikalkulasi.",
"session" => $session,
"totals" => $totals,
]);
}
/**
* Endpoint untuk menerima data dari IoT device
* POST /api/store-data
* Data hanya diterima jika ada sesi aktif
*/
public function storeData(Request $request)
{
// Cek apakah ada sesi aktif
$session = SortingSession::active()->first();
if (!$session) {
return response()->json(
[
"success" => false,
"message" =>
"Tidak ada sesi sortir yang aktif. Tekan Start terlebih dahulu.",
],
403,
);
}
$validated = $request->validate([
"raw_banana_weight" => "required|numeric|min:0",
"ripe_banana_weight" => "required|numeric|min:0",
"raw_orange_weight" => "required|numeric|min:0",
"ripe_orange_weight" => "required|numeric|min:0",
]);
// 1. Abaikan data jika semua berat adalah 0 (tidak ada buah di timbangan)
$hasWeight =
(float) $validated["raw_banana_weight"] > 0 ||
(float) $validated["ripe_banana_weight"] > 0 ||
(float) $validated["raw_orange_weight"] > 0 ||
(float) $validated["ripe_orange_weight"] > 0;
if (!$hasWeight) {
return response()->json(
[
"success" => true,
"message" =>
"Data dilewati karena semua berat bernilai 0 (timbangan kosong).",
"data" => null,
],
200,
);
}
// 2. Cegah duplikasi data buah yang sama dalam waktu < 3 detik
$lastRecord = SortingHistory::where(
"session_id",
(string) $session->_id,
)
->latest("classification_date")
->first();
if ($lastRecord) {
$timeDiff = now()->diffInSeconds($lastRecord->classification_date);
if ($timeDiff < 3) {
$isDuplicate =
abs(
(float) $lastRecord->raw_banana_weight -
(float) $validated["raw_banana_weight"],
) < 0.1 &&
abs(
(float) $lastRecord->ripe_banana_weight -
(float) $validated["ripe_banana_weight"],
) < 0.1 &&
abs(
(float) $lastRecord->raw_orange_weight -
(float) $validated["raw_orange_weight"],
) < 0.1 &&
abs(
(float) $lastRecord->ripe_orange_weight -
(float) $validated["ripe_orange_weight"],
) < 0.1;
if ($isDuplicate) {
return response()->json(
[
"success" => true,
"message" =>
"Data dilewati karena dideteksi sebagai duplikat dari buah yang sama.",
"data" => null,
],
200,
);
}
}
}
$validated["session_id"] = (string) $session->_id;
$validated["classification_date"] = now();
$record = SortingHistory::create($validated);
return response()->json(
[
"success" => true,
"data" => $record,
],
201,
);
}
/**
* Ambil daftar foto buah terbaru
* GET /api/foto
*/
public function latestFoto(Request $request)
{
$activeSession = SortingSession::active()->first();
$query = FruitImage::latest("captured_at");
// Filter by label jika ada
if ($request->has("label") && $request->label) {
$query->where("label", $request->label);
}
$this->applyFilters($query, $request, "captured_at", $activeSession);
$fotos = $query
->limit(200)
->get()
->map(function ($item) {
return [
"id" => (string) $item->_id,
"label" => $item->label,
"image_base64" => $item->image_base64,
"image_size" => $item->image_size,
"captured_at" => $item->captured_at,
];
});
return response()->json(["fotos" => $fotos]);
}
/**
* Apply shared filter logic to a query builder.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Http\Request $request
* @param string $dateColumn The date column to filter on (e.g. 'classification_date' or 'captured_at')
* @param \App\Models\SortingSession|null $activeSession
*/
private function applyFilters(
$query,
Request $request,
string $dateColumn,
$activeSession = null,
): void {
$filter = $request->input("filter");
switch ($filter) {
case "session":
$sessionId = $request->input("session_id");
if ($sessionId) {
$query->where("session_id", $sessionId);
} else {
$this->applyDefaultSessionFilter($query, $activeSession);
}
break;
case "today":
$query
->where($dateColumn, ">=", Carbon::today()->startOfDay())
->where($dateColumn, "<=", Carbon::today()->endOfDay());
break;
case "week":
$query->where(
$dateColumn,
">=",
Carbon::now()->subDays(7)->startOfDay(),
);
break;
case "month":
$query->where(
$dateColumn,
">=",
Carbon::now()->subDays(30)->startOfDay(),
);
break;
case "custom":
if ($request->has("date_from") && $request->date_from) {
$query->where(
$dateColumn,
">=",
Carbon::parse($request->date_from)->startOfDay(),
);
}
if ($request->has("date_to") && $request->date_to) {
$query->where(
$dateColumn,
"<=",
Carbon::parse($request->date_to)->endOfDay(),
);
}
break;
default:
// Backward compatibility: no filter param uses active/latest session
$this->applyDefaultSessionFilter($query, $activeSession);
break;
}
}
/**
* Apply the default session-based filter (active session or latest completed).
*/
private function applyDefaultSessionFilter($query, $activeSession): void
{
if ($activeSession) {
$query->where("session_id", (string) $activeSession->_id);
} else {
$latestCompleted = SortingSession::where("status", "completed")
->latest("ended_at")
->first();
if ($latestCompleted) {
$query->where("session_id", (string) $latestCompleted->_id);
} else {
$query->where("session_id", "non-existent-session-id");
}
}
}
/**
* Cek status sesi saat ini
* GET /api/session/status
*/
public function sessionStatus()
{
$session = SortingSession::active()->first();
return response()->json([
"active" => $session ? true : false,
"session" => $session
? [
"id" => (string) $session->_id,
"status" => $session->status,
"started_at" => $session->started_at,
]
: null,
]);
}
}