orderBy("id_lokasi")->get(); return view("report.okupansi-area", compact("area")); } public function data(Request $request) { $request->validate( [ "tgl_awal" => ["nullable", "date_format:Y-m-d"], "tgl_akhir" => ["nullable", "date_format:Y-m-d", "after_or_equal:tgl_awal", "before_or_equal:today"], "area" => ["nullable", "string", "max:10"], ], [ "tgl_akhir.after_or_equal" => "Periode akhir tidak boleh lebih awal dari periode awal.", "tgl_akhir.before_or_equal" => "Periode akhir tidak boleh melewati hari ini.", ] ); $tglAkhir = $request->input("tgl_akhir", Carbon::today()->toDateString()); $tglAwal = $request->input("tgl_awal", Carbon::parse($tglAkhir)->subDays(6)->toDateString()); $start = Carbon::parse($tglAwal)->startOfDay(); $end = Carbon::parse($tglAkhir)->startOfDay(); if ($start->diffInDays($end) > 370) { return response()->json(["message" => "Rentang maksimal 370 hari, persempit periodenya."], 422); } $areaFilter = $request->input("area"); $areas = MasterLokasi::query() ->when($areaFilter && $areaFilter !== "all", function ($query) use ($areaFilter) { $query->where("id_lokasi", $areaFilter); }) ->orderBy("prioritas") ->orderBy("id_lokasi") ->get(); $dateList = []; for ($date = $start->copy(); $date->lte($end); $date->addDay()) { $dateList[] = $date->toDateString(); } $masukMap = $this->eventMap($request, $areaFilter, $tglAwal, $tglAkhir, 0, "waktu_masuk", [0, 1]); $keluarMap = $this->eventMap($request, $areaFilter, $tglAwal, $tglAkhir, 1, "waktu_keluar", [0]); $today = Carbon::today()->toDateString(); $currentHour = (int) Carbon::now()->format("G"); $areaRows = []; foreach ($areas as $area) { $capacity = max(0, (int) $area->max_space); $stats = []; $totalMasuk = 0; $totalKeluar = 0; $fullDays = 0; for ($hour = 0; $hour < 24; $hour++) { $stats[$hour] = [ "sum" => 0, "count" => 0, "max" => 0, "min" => null, "masuk" => 0, "keluar" => 0, "hari_penuh" => 0, ]; } foreach ($dateList as $date) { $occupancy = 0; $isFull = false; $maxHour = $date === $today ? $currentHour : 23; for ($hour = 0; $hour <= $maxHour; $hour++) { $masuk = (int) ($masukMap[$area->id_lokasi][$date][$hour] ?? 0); $keluar = (int) ($keluarMap[$area->id_lokasi][$date][$hour] ?? 0); $occupancy = max(0, min($capacity, $occupancy + $masuk - $keluar)); $stats[$hour]["sum"] += $occupancy; $stats[$hour]["count"]++; $stats[$hour]["max"] = max($stats[$hour]["max"], $occupancy); $stats[$hour]["min"] = $stats[$hour]["min"] === null ? $occupancy : min($stats[$hour]["min"], $occupancy); $stats[$hour]["masuk"] += $masuk; $stats[$hour]["keluar"] += $keluar; $totalMasuk += $masuk; $totalKeluar += $keluar; if ($capacity > 0 && $occupancy >= $capacity) { $stats[$hour]["hari_penuh"]++; $isFull = true; } } if ($isFull) { $fullDays++; } } $hourRows = []; $totalSum = 0; $totalCount = 0; for ($hour = 0; $hour < 24; $hour++) { $stat = $stats[$hour]; $count = max(1, $stat["count"]); $average = round($stat["sum"] / $count, 1); $percent = $capacity > 0 ? round(($average / $capacity) * 100, 1) : 0; $hourRows[] = [ "jam" => $hour, "label" => str_pad((string) $hour, 2, "0", STR_PAD_LEFT) . ":00", "rata" => $average, "maks" => $stat["max"], "min" => $stat["min"] ?? 0, "persen" => $percent, "status" => $this->statusOkupansi($percent, $capacity), "hari_penuh" => $stat["hari_penuh"], "hari_dianalisis" => $stat["count"], "masuk" => $stat["masuk"], "keluar" => $stat["keluar"], ]; $totalSum += $stat["sum"]; $totalCount += $stat["count"]; } $peak = $hourRows[0]; $quiet = $hourRows[0]; $mostFull = null; foreach ($hourRows as $row) { if ($row["rata"] > $peak["rata"]) { $peak = $row; } if ($row["rata"] < $quiet["rata"]) { $quiet = $row; } if ($row["hari_penuh"] > 0 && ($mostFull === null || $row["hari_penuh"] > $mostFull["hari_penuh"])) { $mostFull = $row; } } $averageOccupancy = $totalCount > 0 ? round($totalSum / $totalCount, 1) : 0; $averagePercent = $capacity > 0 ? round(($averageOccupancy / $capacity) * 100, 1) : 0; $areaRows[] = [ "id_lokasi" => $area->id_lokasi, "nama_lokasi" => $area->nama_lokasi, "kapasitas" => $capacity, "sisa" => (int) $area->sisa, "total_masuk" => $totalMasuk, "total_keluar" => $totalKeluar, "rata_okupansi" => $averageOccupancy, "rata_persen" => $averagePercent, "status" => $this->statusOkupansi($peak["persen"], $capacity), "puncak" => $peak, "kosong" => $quiet, "jam_penuh" => $mostFull, "hari_penuh" => $fullDays, "jam" => $hourRows, ]; } $areaTerpadat = null; foreach ($areaRows as $row) { if ($areaTerpadat === null || $row["puncak"]["persen"] > $areaTerpadat["puncak"]["persen"] || ($row["puncak"]["persen"] === $areaTerpadat["puncak"]["persen"] && $row["puncak"]["rata"] > $areaTerpadat["puncak"]["rata"])) { $areaTerpadat = $row; } } $hourAggregate = array_fill(0, 24, ["sum" => 0, "count" => 0]); foreach ($areaRows as $row) { foreach ($row["jam"] as $hour) { $weight = $hour["hari_dianalisis"]; $hourAggregate[$hour["jam"]]["sum"] += $hour["rata"] * $weight; $hourAggregate[$hour["jam"]]["count"] += $weight; } } $jamPadat = null; $jamLengang = null; foreach ($hourAggregate as $hour => $aggregate) { $average = $aggregate["count"] > 0 ? $aggregate["sum"] / $aggregate["count"] : 0; if ($jamPadat === null || $average > $jamPadat["rata"]) { $jamPadat = ["jam" => $hour, "label" => str_pad((string) $hour, 2, "0", STR_PAD_LEFT) . ":00", "rata" => round($average, 1)]; } if ($jamLengang === null || $average < $jamLengang["rata"]) { $jamLengang = ["jam" => $hour, "label" => str_pad((string) $hour, 2, "0", STR_PAD_LEFT) . ":00", "rata" => round($average, 1)]; } } return response()->json([ "status" => true, "tgl_awal" => $tglAwal, "tgl_akhir" => $tglAkhir, "data" => $areaRows, "ringkasan" => [ "jumlah_area" => count($areaRows), "area_pernah_penuh" => count(array_filter($areaRows, fn ($row) => $row["hari_penuh"] > 0)), "area_terpadat" => $areaTerpadat, "jam_padat" => $jamPadat, "jam_lengang" => $jamLengang, ], ]); } private function eventMap(Request $request, ?string $areaFilter, string $tglAwal, string $tglAkhir, int $jenis, string $timeColumn, array $statuses): array { $rows = DB::table("transaksi_parkir as tp") ->join("nama_pos as np", "np.id", "=", $jenis === 0 ? "tp.id_pintu_masuk" : "tp.id_pintu_keluar") ->join("master_lokasi as ml", "ml.id_lokasi", "=", "np.area") ->whereIn("tp.status", $statuses) ->where("np.jenis", $jenis) ->whereNotNull("tp.{$timeColumn}") ->whereBetween("tp.{$timeColumn}", [$tglAwal . " 00:00:00", $tglAkhir . " 23:59:59"]) ->when($areaFilter && $areaFilter !== "all", function ($query) use ($areaFilter) { $query->where("ml.id_lokasi", $areaFilter); }) ->selectRaw("ml.id_lokasi as area_id") ->selectRaw("tp.{$timeColumn}::date as tanggal") ->selectRaw("extract(hour from tp.{$timeColumn})::int as jam") ->selectRaw("count(*) as jumlah") ->groupByRaw("ml.id_lokasi, tp.{$timeColumn}::date, extract(hour from tp.{$timeColumn})") ->get(); $map = []; foreach ($rows as $row) { $map[$row->area_id][substr((string) $row->tanggal, 0, 10)][(int) $row->jam] = (int) $row->jumlah; } return $map; } private function statusOkupansi(float $percent, int $capacity): string { if ($capacity <= 0) { return "Kapasitas belum diisi"; } if ($percent >= 100) { return "Penuh"; } if ($percent >= 80) { return "Hampir penuh"; } if ($percent >= 50) { return "Ramai"; } if ($percent > 0) { return "Lengang"; } return "Kosong"; } }