Rapihin setup/member/lot-parking + fitur intercom, exclude WIP controller & file sampah dari git

Kumpulan perubahan yang udah numpuk di working tree: refactor Setup
(Gate/Vehicle/Member), rapihin memberin & stiker, service parking baru
(ParkingUtilsServices, NetworkService), model/migration lot-parking &
DHCP reservation, dan fitur baru Intercom (controller/model/migration/
view).

ReportTransaksiController versi lokal ini WIP (~90%, masih didebug) -
versi yang bener ada di server, jadi dikeluarin dari git (ditambahin
ke .gitignore) biar gak numpuk nindihin punya server pas git pull di
sana. Sekalian keluarin dump mentah tabel softseting (1.8MB, nyasar
ke-commit), file .bak, dan arsip ad-hoc (api.tgz, c.zip) yang emang
bukan source code.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EAJc2mrpj2cSTe4oowLPzQ
This commit is contained in:
2026-10-04 07:15:54 +07:00
co-authored by Claude Sonnet 5
parent efdea9d739
commit 2a9b28581c
91 changed files with 5716 additions and 2470 deletions
+12
View File
@@ -17,3 +17,15 @@ yarn-error.log
/.vscode
/Data_lokasi
/public/js
# ReportTransaksiController yg bener/lebih lengkap ada di server (90%,
# masih didebug di sana) - versi lokal ini WIP eksperimen, jangan sampe
# ke-push & numpuk balik nindihin punya server pas git pull di server.
/app/Http/Controllers/Parking/Report/ReportTransaksiController.php
# File backup manual (.bak) & dump/arsip mentah yang numpuk gak sengaja -
# bukan source code, gak perlu ditrack git.
*.bak
/softseting
*.tgz
*.zip
+15
View File
@@ -0,0 +1,15 @@
<?php
if (!function_exists("rupiah")) {
function rupiah($nominal)
{
return "Rp. " . number_format($nominal, 0, ",", ".");
}
}
if (!function_exists("percent")) {
function percent($value)
{
return number_format($value, 2) . "%";
}
}
@@ -34,9 +34,8 @@ public function index(Request $request)
if (!array_key_exists('gate', $_GET)) {
return response()->json([
'status' => 'success',
// 'desc' => 'Parameter undefined',
'details' => $location
],400);
]);
}
@@ -0,0 +1,128 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\IntercomCall;
use App\Models\IntercomDevice;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
/**
* Modul Intercom: reuse pola ParkingOperationController.
* Laravel HANYA urus signaling + log, BUKAN relay audio.
* Audio diurus Go relay (UDP), Laravel cuma simpan metadata + path rekaman.
*/
class IntercomController extends Controller
{
public function devices()
{
return IntercomDevice::orderBy('device_id')->get();
}
public function queue()
{
return IntercomCall::whereIn('status', ['waiting', 'active'])
->orderBy('requested_at')
->limit(20)->get();
}
public function history(Request $request)
{
return IntercomCall::orderByDesc('id')->limit($request->input('limit', 50))->get();
}
// Dipanggil Go relay saat ESP32 kirim MQTT intercom/request
public function request(Request $request)
{
$data = $request->validate([
'device_id' => 'required|string|max:20',
'arah' => 'sometimes|string|in:in,out',
]);
$deviceId = strtoupper($data['device_id']);
$device = IntercomDevice::firstOrCreate(
['device_id' => $deviceId],
['nama' => $deviceId, 'status' => 'idle']
);
// Kalau device lagi active, tetap masuk antrean (tidak ditolak)
$call = IntercomCall::create([
'device_id' => $deviceId,
'arah' => $data['arah'] ?? 'in',
'status' => 'waiting',
'requested_at' => now(),
]);
$device->update(['status' => 'calling', 'last_seen_at' => now()]);
Log::info("Intercom request {$deviceId} call_id={$call->id}");
return response()->json(['status' => true, 'call_id' => $call->id, 'device_id' => $deviceId]);
}
// Admin klik [Terima] di dashboard
public function accept(Request $request)
{
$data = $request->validate(['call_id' => 'required|integer']);
$call = IntercomCall::findOrFail($data['call_id']);
// Tutup sesi active lain di device yang sama (single private session)
IntercomCall::where('device_id', $call->device_id)
->where('status', 'active')->update(['status' => 'ended', 'ended_at' => now(), 'ended_by' => 'admin-switch']);
$call->update(['status' => 'active', 'started_at' => now()]);
IntercomDevice::where('device_id', $call->device_id)->update(['status' => 'active']);
// Go relay yang bertugas publish MQTT intercom/invite/{DEVICE} + buka UDP port.
// Laravel cuma ubah status, tidak publish MQTT langsung.
return response()->json(['status' => true, 'call' => $call]);
}
public function end(Request $request)
{
$data = $request->validate([
'call_id' => 'required|integer',
'ended_by' => 'sometimes|string|max:20',
'recording_path' => 'sometimes|string|max:255',
]);
$call = IntercomCall::findOrFail($data['call_id']);
$now = now();
$durasi = $call->started_at ? $now->diffInSeconds($call->started_at) : ($call->requested_at ? $now->diffInSeconds($call->requested_at) : 0);
$call->update([
'status' => $call->status === 'waiting' && ($data['ended_by'] ?? '') === 'timeout' ? 'missed' : 'ended',
'ended_at' => $now,
'durasi_detik' => $durasi,
'ended_by' => $data['ended_by'] ?? 'admin',
'recording_path' => $data['recording_path'] ?? $call->recording_path,
]);
// Balikin status device ke idle kalau tidak ada sesi lain
$masihAktif = IntercomCall::where('device_id', $call->device_id)->whereIn('status', ['waiting', 'active'])->exists();
if (!$masihAktif) {
IntercomDevice::where('device_id', $call->device_id)->update(['status' => 'idle']);
}
return response()->json(['status' => true, 'call' => $call]);
}
// Heartbeat / LWT dari Go relay: ESP32 online/offline
public function heartbeat(Request $request)
{
$data = $request->validate([
'device_id' => 'required|string|max:20',
'status' => 'sometimes|string|max:20',
'ip_address' => 'sometimes|string|max:45',
]);
IntercomDevice::updateOrCreate(
['device_id' => strtoupper($data['device_id'])],
['status' => $data['status'] ?? 'idle', 'ip_address' => $data['ip_address'] ?? null, 'last_seen_at' => now()]
);
return response()->json(['status' => true]);
}
}
@@ -0,0 +1,19 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use App\Models\SoftSettings;
use Illuminate\Http\Request;
class LocationSiteInfoController extends Controller
{
public function getLocation()
{
$site = SoftSettings::first();
return response->json([
"status" => true,
"data" => $site,
]);
}
}
@@ -1,125 +0,0 @@
<?php
namespace App\Http\Controllers\Api\V1;
use App\Http\Controllers\Controller;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class MemberinController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
Log::info($id);
$dateNow = Carbon::now();
$data = DB::table('mergetransaksistikerdetail as a')
->select('a.*','jm.nama as vehicle')
->leftjoin('jenis_mobil as jm','jm.id','=','a.jenis_mobil')
->where('nopol', $id)
->orderByDesc('tgl_edited')
->first();
$akhir = Carbon::parse($data->akhir);
if(!$data) {
return response()->json([
'status'=>false,
'message'=>'Member tidak ditemukan'
]);
}
if($dateNow->greaterThan($akhir)){
return response()->json([
'status' => 'expired',
'expired' => true,
'message' => 'Member expired',
'data' => $data
]);
} else {
return response()->json([
'status' => 'active',
'expired' => false,
'message' => 'Member aktif',
'data' => $data
]);
}
Log::info(json_encode($data));
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}
@@ -0,0 +1,230 @@
<?php
namespace App\Http\Controllers\Api\V1\ParkingLot;
use App\Http\Controllers\Controller;
use App\Models\LotParking\SiteParking;
use App\Models\LotParking\LotParkingLog;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class ParkingLotController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$getArea = SiteParking::all(); // Mengambil semua data dari model SiteParking
$parkingDataByArea = []; // Array untuk menyimpan hasil query berdasarkan area
foreach ($getArea as $lantai) {
$spaceParking = $lantai->space_parking;
// Array untuk menyimpan data parking per lantai
$parkingData = [];
for ($lotNumber = 1; $lotNumber <= $spaceParking; $lotNumber++) {
// Melakukan query pada LotParkingLog berdasarkan area dan lot_number
$log = LotParkingLog::where("area", $lantai->sitearea_id)
->where("lot_number", $lotNumber)
->select("lot_number", "status")
->first();
// Jika data tidak ditemukan, buat object baru dengan lot_number dari pengulangan for
if (!$log) {
$log = (object) [
"lot_number" => $lotNumber,
"status" => 0,
];
}
// Menyimpan hasil query dalam array
$parkingData[] = $log;
}
// Menyimpan data parking per lantai dalam array berdasarkan area
$parkingDataByArea[] = [
"lantai" => $lantai->sitearea_id,
"data" => $parkingData,
];
}
// Melakukan dump and die untuk melihat hasil query sementara
// dd(json_encode($parkingDataByArea));
// dd($getArea->toJson());
return view("headoffice.space", [
"areas" => $getArea,
"parkingDataByArea" => $parkingDataByArea,
]);
return view("headoffice.space");
}
public function getParkingData()
{
Log::info("getParkingData method called");
$getArea = SiteParking::all(); // Mengambil semua data dari model SiteParking
$parkingDataByArea = []; // Array untuk menyimpan hasil query berdasarkan area
foreach ($getArea as $lantai) {
$spaceParking = $lantai->space_parking;
// Array untuk menyimpan data parking per lantai
$parkingData = [];
for ($lotNumber = 1; $lotNumber <= $spaceParking; $lotNumber++) {
// Melakukan query pada LotParkingLog berdasarkan area dan lot_number
$log = LotParkingLog::where("area", $lantai->sitearea_id)
->where("lot_number", $lotNumber)
->select("lot_number", "status")
->first();
// Jika data tidak ditemukan, buat object baru dengan lot_number dari pengulangan for
if (!$log) {
$log = (object) [
"lot_number" => $lotNumber,
"status" => 0,
];
}
// Menyimpan hasil query dalam array
$parkingData[] = $log;
}
// Menyimpan data parking per lantai dalam array berdasarkan area
$parkingDataByArea[] = [
"lantai" => $lantai->sitearea_id,
"data" => $parkingData,
];
Log::info($parkingDataByArea);
}
return response()->json($parkingDataByArea);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$area = $request->site_area;
$lotNumber = $request->lot_number;
Log::info($area);
Log::info($lotNumber);
$exisLot = LotParkingLog::where("area", $area)->where("lot_number", $lotNumber)->where("status", 1)->first();
if (!$exisLot) {
$LotParking = LotParkingLog::create([
"area" => $area,
"lot_number" => $lotNumber,
"status" => 1,
"update_at" => null,
]);
Log::info("Lot " . $lotNumber . " di " . $area . " terisi.");
return response()->json([
"status" => "success",
"message" => "Lot No " . $lotNumber . " terisi",
"data" => $LotParking,
]);
} else {
return response()->json([
"status" => "error",
"message" => "Kendaraan belum keluar",
]);
}
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request)
{
Log::info(json_encode($request->all()));
$area = $request->site_area;
$lotNumber = $request->lot_number;
Log::info($area);
Log::info($lotNumber);
$exisLot = LotParkingLog::where("area", $area)
->where("lot_number", $lotNumber)
->where("status", 1)
->orderBy("updated_at", "desc")
->first();
if (!$exisLot) {
return response()->json([
"status" => "error",
"message" => "Lot masih kosong",
]);
} else {
$exisLot->update([
"status" => 0,
]);
Log::info("Lot " . $lotNumber . " di " . $area . " keluar area.");
return response()->json([
"status" => "success",
"message" => "Kendaraan telah keluar",
"data" => $exisLot,
]);
}
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}
@@ -16,19 +16,19 @@ class SelfMemberinController extends Controller
*/
public function index(Request $request)
{
$jenis = $request->query('jenis');
$jenis = $request->query("jenis");
Log::info($jenis);
$tarifStiker = DB::table('tarif_stiker');
$tarifStiker = DB::table("tarif_stiker");
if ($jenis) {
$tarifStiker->where('stasiunkerja_id', $jenis);
$tarifStiker->where("stasiunkerja_id", $jenis);
}
$produk = $tarifStiker->get();
Log::info($produk);
return view('apps.member.index', compact('produk'));
return view("apps.memberin.index", compact("produk"));
}
/**
@@ -61,10 +61,7 @@ public function store(Request $request)
public function show($id)
{
Log::info($id);
$tarifStiker = DB::table('tarif_stiker')
->where('stasiunkerja_id', $id)
->orderby('tarif')
->get();
$tarifStiker = DB::table("tarif_stiker")->where("stasiunkerja_id", $id)->orderby("tarif")->get();
return $tarifStiker;
}
@@ -6,6 +6,7 @@
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
class LoginController extends Controller
@@ -56,4 +57,11 @@ protected function credentials (Request $request)
'password' => $request->input('password'),
];
}
protected function sendFailedLoginResponse(Request $request)
{
throw ValidationException::withMessages([
'login' => 'Username atau password tidak sesuai.',
]);
}
}
@@ -0,0 +1,54 @@
<?php
namespace App\Http\Controllers\Intercom;
use App\Http\Controllers\Controller;
use App\Models\IntercomCall;
class UIController extends Controller
{
// Dashboard operator pusat. Data real-time diambil via JS polling ke /api/v1/intercom/*.
public function index()
{
return view('Intercom.UI.index');
}
// Streaming/download rekaman per sesi. File ditulis Go relay (STORE_DIR).
public function recording($id)
{
$call = IntercomCall::findOrFail($id);
if (!$call->recording_path) {
abort(404, 'Belum ada rekaman untuk sesi ini');
}
$path = $call->recording_path;
// Amankan: hanya basename, cari di kandidat folder (relatif project + /opt).
$candidates = [
$path,
storage_path('app/intercom/' . basename($path)),
'/opt/intercom-relay/storage/' . basename($path),
];
$real = null;
foreach ($candidates as $c) {
if ($c && is_file($c)) {
$real = $c;
break;
}
}
if (!$real) {
abort(404, 'File rekaman tidak ditemukan di server: ' . basename($path));
}
// File dari Go relay berupa raw PCM 16kHz mono 16-bit.
// Disajikan sebagai octet-stream biar bisa di-download / ffplay.
// Nanti kalau relay sudah tulis header WAV, ganti ke audio/wav.
return response()->file($real, [
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="' . basename($real) . '"',
]);
}
}
@@ -1,84 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class LevelMenuController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}
@@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers\Parking\Audit;
use App\Models\TransaksiParkir;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Yajra\DataTables\Facades\DataTables;
class AuditReportController extends Controller
{
public function index()
{
return view("Parking.Audit.index");
}
public function data(Request $request)
{
$query = TransaksiParkir::query()
->from("transaksi_parkir as tp")
->leftJoin("pegawai as p", "p.nomer", "=", "tp.adm");
// Tanggal wajib
$query->whereBetween("waktu_keluar", [$request->start_date . " 00:00:00", $request->end_date . " 23:59:59"]);
// Status
if ($request->filled("status_transaksi")) {
$query->where("status_transaksi", $request->status_transaksi);
} else {
// Default hanya -1 dan -2
$query->whereIn("status_transaksi", [0, -1, -2]);
}
// Alasan tidak boleh NULL
if ($request->filter_alasan == "1") {
$query->whereNotNull("alasan");
}
$totalTransaksi = (clone $query)->count();
$totalBayar = (clone $query)->sum("bayar_keluar");
$data = $query
->select([
"tp.id",
"no_pol",
"waktu_masuk",
"waktu_keluar",
"status_transaksi",
"bayar_keluar",
"alasan",
"cara_bayar",
"p.nama as operator",
])
->orderByDesc("waktu_keluar")
->get();
return response()->json([
"response" => true,
"data" => $data,
"summary" => [
"total_transaksi" => $totalTransaksi,
"total_bayar" => $totalBayar,
],
]);
}
}
@@ -3,11 +3,14 @@
namespace App\Http\Controllers\Parking;
use App\Http\Controllers\Controller;
use App\Models\MemberCard;
use App\Models\SoftSettings;
use App\Models\StikerTrans;
use App\Models\StikerTransDetail;
use App\Models\MemberCard;
use App\Services\MakeStikerServices;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
@@ -35,12 +38,22 @@ public function index(Request $request)
public function getMember(Request $request)
{
// Log::info(json_encode("nopol : " . $request->nopol));
Log::info(json_encode("nama : " . $request->input("search-by")));
Log::info(json_encode("value : " . $request->input("search_value")));
Log::info(json_encode("isActive : " . $request->input("isActive")));
// Log::info(json_encode("veh : " . $request->vehicle));
// Log::info(json_encode("dept : " . $request->depart));
// Log::info(json_encode("mem : " . $request->member));
// Log::info(json_encode("mem : " . $request->jenismember));
// Log::info(json_encode("trans : " . $request->notrans));
// Log::info(json_encode("isactive : " . gettype($request->isactive)));
$searchBy = $request->input("search-by");
$searchValue = trim($request->input("search_value", ""));
$memberType = $request->input("member_type");
$unitKerja = $request->input("departement");
$vehicle = $request->input("vehicle");
$isActive = $request->input("isActive");
if ($request->ajax()) {
$dataStiker = DB::table("transaksi_stiker as ts")
->leftJoin("detail_transaksi_stiker as dts", "dts.notrans", "=", "ts.notrans")
@@ -55,40 +68,47 @@ public function getMember(Request $request)
jm.nama vehicle, dts.jenis_member, cm.no_card as no_card'),
)
->distinct();
// ->select(DB::raw('ts.notrans as notrans , ts.nama , ts.alamat as alamat , ts.telepon as telepon , ts.awal as awal , ts.akhir as akhir, sk.nama as unit_kerja, jl.nama as jenis_member, ts.no_id'))
if ($request->nama) {
$dataStiker = $dataStiker->where("ts.nama", "like", "%" . $request->nama . "%");
if ($searchBy && $searchValue !== "") {
switch ($searchBy) {
case "nama":
$nama = strtolower($searchValue);
$NAMA = strtoupper($searchValue);
$dataStiker->where("ts.nama", "ILIKE", "%{$searchValue}%");
break;
case "alamat":
$dataStiker->where("ts.alamat", "like", "%{$searchValue}%");
break;
case "nopol":
$dataStiker->where("dts.nopol", "like", "%{$searchValue}%");
break;
case "nocard":
$dataStiker->where("cm.nocard", "like", "%{$searchValue}%");
break;
case "notrans":
$dataStiker->where("ts.notrans", "like", "%{$searchValue}%");
break;
// case "":
// $dataStiker->where("ts.nama", "like", "%{$searchValue}%");
// break;
}
}
if ($request->notrans) {
$dataStiker = $dataStiker->where("ts.notrans", "like", "%" . $request->notrans . "%");
if ($memberType) {
$dataStiker->where("ts.jenis_langganan", $memberType);
}
if ($request->nopol) {
$dataStiker = $dataStiker->where("ts.unit_kerja", "like", "%" . $request->depart . "%");
if ($unitKerja) {
$dataStiker->where("ts.unit_kerja", $unitKerja);
}
if ($request->member) {
$dataStiker = $dataStiker->where("ts.jenis_langganan", "like", "%" . $request->member . "%");
if ($vehicle) {
$dataStiker->where("dts.jenis_mobil", $vehicle);
}
if ($request->nopol) {
$dataStiker = $dataStiker->where("dts.nopol", "like", "%" . $request->nopol . "%");
if ($request->input("isActive") == "1") {
$dataStiker->whereRaw("NOW() BETWEEN ts.awal AND ts.akhir");
}
if ($request->vehicle) {
$dataStiker = $dataStiker->where("dts.jenis_mobil", "like", "%" . $request->vehicle . "%");
}
if ($request->nocard) {
$dataStiker = $dataStiker->where("cm.no_card", "like", "%" . $request->nocard . "%");
}
if ($request->isactive === "1") {
$dataStiker = $dataStiker->whereRaw("now() between ts.awal and ts.akhir");
}
// Log::info($dataStiker->toSql());
$dataStiker = $dataStiker->get();
@@ -132,22 +152,47 @@ public function checkField(Request $request)
return response()->json(["response" => true, "message" => "Data sudah ada"]);
}
public function store(Request $request)
public function store(Request $request, MakeStikerServices $makeStiker)
{
Log::info("Data yang diterima : " . json_encode($request->all()));
$mountYearNow = Carbon::now()->format("m/Y");
$dataStiker = $request->input("formData");
$dataVehicle = $request->input("nopolData");
$dataCard = $request->input("cardNumber");
Log::info("Data Stiker : " . json_encode($dataStiker));
Log::info("Data Kendaraan : " . json_encode($dataVehicle));
Log::info("Data kartu : " . json_encode($dataCard));
// $cleanedData = stripslashes(trim($data));
// Log::debug('Modified : ' . json_encode($cleanedData));
// $dataStiker = json_decode($cleanedData, false);
// Log::info('Debuggind data : ' . json_encode($dataStiker));
$getTrans = DB::table("indexnum")
->whereIn("nama", ["TRX", "IDN"])
->pluck("value", "nama");
$noTrans = $getTrans->get("TRX");
$noID = $getTrans->get("IDN");
// Log::info("Get Trans : " . json_encode($getTrans));
// Log::info("No Trans : " . $noTrans);
// Log::info("No ID : " . $noID);
$noTransFormat = str_pad($noTrans, 5, "0", STR_PAD_LEFT);
$noIDFormat = str_pad($noID, 5, "0", STR_PAD_LEFT);
$getLocation = SoftSettings::first();
Log::info("Namalokasi : " . json_encode($getLocation->namalokasi));
$ignore = ["RS", "PT", "CV", "TBK"];
$nama = preg_replace("/[^A-Za-z0-9 ]/", "", $getLocation->namalokasi);
$kata = preg_split("/\s+/", trim($nama));
$identity = "";
foreach ($kata as $k) {
if (in_array(strtoupper($k), $ignore)) {
continue;
}
$identity .= strtoupper($k[0]);
}
$randomNumber = strtoupper(Str::random(6));
$newTrans = "TRX-" . $identity . "-" . Carbon::now()->format("Ymd-His") . "-" . $randomNumber;
$newID = "IDN-" . $identity . "-" . Carbon::now()->format("Ymd-His") . "-" . $randomNumber;
// $noid = $seqId->value . "-IDT-" . $mountYearNow;
// $validate = Validator::make($request->all(),[
// 'nama' => 'required|string|unique',
// // 'nopol' => 'required|string|unique:detail_transaksi_stiker,nopol',
@@ -158,84 +203,131 @@ public function store(Request $request)
// return response()->json(['response' => false, 'message' => 'pastikan nama, Nomor Polisi, dan data kartu sudah terisi']);
// }
$dateNow = Carbon::now()->format("Y-m-d H:m:s");
$seqTrans = DB::table("indexnum")->select("value")->where("nama", "sticker")->first();
$seqId = DB::table("indexnum")->select("value")->where("nama", "identitas")->first();
// $dateNow = Carbon::now()->format("Y-m-d H:m:s");
// $seqTrans = DB::table("indexnum")->select("value")->where("nama", "sticker")->first();
// $seqId = DB::table("indexnum")->select("value")->where("nama", "identitas")->first();
$notrans = $seqTrans->value . "/STK/" . $mountYearNow;
$noid = $seqId->value . "/IDT/" . $mountYearNow;
$user = Auth::user();
// $notrans = $seqTrans->value . "/STK/" . $mountYearNow;
// $noid = $seqId->value . "/IDT/" . $mountYearNow;
// $user = Auth::user();
// // Log::info($user);
$dataStiker = $request->input("formData");
$dataVehicle = $request->input("nopolData");
$dataCard = $request->input("cardNumber");
Log::info("Data Stiker : " . json_encode($dataStiker));
Log::info("Data Kendaraan : " . json_encode($dataVehicle));
Log::info("Data kartu : " . json_encode($dataCard));
if ($dataStiker["periode"]) {
$periode = explode(" - ", $dataStiker["periode"]);
Log::info("Parcing Periode : " . json_encode($periode));
$awal = Carbon::createFromFormat("d-m-Y H:i", $periode[0]);
$akhir = Carbon::createFromFormat("d-m-Y H:i", $periode[1]);
} else {
$awal = Carbon::createFromFormat("d-m-Y H:i", $dataStiker["awal"]);
$akhir = Carbon::createFromFormat("d-m-Y H:i", $dataStiker["akhir"]);
}
Log::info("Awal : " . $awal . " Akhir : " . $akhir);
// Log::info($user);
try {
$dataStiker = [
"jenis_transaksi" => $dataStiker["jenis_transaksi"], // 0 = transaksi baru , 1 = perpanjangan
"notrans" => $notrans,
"no_id" => $noid,
"nama" => $dataStiker["nama"],
"alamat" => $dataStiker["address"] ?? "",
"telepon" => $dataStiker["phone"] ?? "",
"unit_kerja" => $dataStiker["depart"],
"jenis_stiker" => $dataStiker["member"],
"awal" => $awal,
"akhir" => $akhir,
"harga" => $dataStiker["harga"] ?? 0,
"keterangan" => $dataStiker["note"] ?? "",
"tanggal" => $dateNow,
"operator" => $user->nomer,
];
$dataStiker["notrans"] = $newTrans;
$dataStiker["no_id"] = $newID;
$dataStiker["awal"] = $awal;
$dataStiker["akhir"] = $akhir;
DB::table("transaksi_stiker")->insert($dataStiker);
if ($dataVehicle) {
foreach ($dataVehicle as $nopol) {
$dataVehicle = [
"notrans" => $notrans,
"nopol" => $nopol["nopol"],
"jenis_mobil" => $nopol["vehicle"],
"jenis_member" => $nopol["member"],
"merk" => $nopol["merk"],
];
DB::table("detail_transaksi_stiker")->insert($dataVehicle);
if (!empty($dataVehicle)) {
foreach ($dataVehicle as &$vehicle) {
$vehicle["notrans"] = $newTrans;
}
} else {
return 0;
}
if ($dataCard) {
foreach ($dataCard as $cardNumber) {
$dataCard = [
"notrans" => $noid,
"no_card" => $cardNumber["nocard"],
"no_urut" => $cardNumber["nourut"],
];
DB::table("card_member")->insert($dataCard);
if (!empty($dataCard)) {
foreach ($dataCard as &$cardNumber) {
$cardNumber["notrans"] = $newID;
}
} else {
return 0;
}
DB::table("indexnum")
->where("nama", "sticker")
->update(["value" => DB::raw("lpad(to_number(value,'9999')+1)::text, 4, '0')")]);
DB::table("indexnum")
->where("nama", "identitas")
->update(["value" => DB::raw("lpad(to_number(value,'9999')+1)::text, 4, '0')")]);
$response = true;
$msg = "Data berhasil di simpan";
} catch (Exception $e) {
$response = false;
$msg = "Gagal menyimpan data " . $e->getMessage() . $e->getLine();
}
$createData = $makeStiker->stikerCreate(
[
"dataSticker" => $dataStiker,
"dataVehicle" => $dataVehicle ?? null,
"dataCard" => $dataCard ?? null,
],
$awal,
$akhir,
);
return response()->json(["response" => $response, "pesan" => $msg]);
Log::info("Status penyimpanan : " . $createData);
// try {
// $dataStiker = [
// "jenis_transaksi" => $dataStiker["jenis_transaksi"], // 0 = transaksi baru , 1 = perpanjangan
// "notrans" => $notrans,
// "no_id" => $noid,
// "nama" => $dataStiker["nama"],
// "alamat" => $dataStiker["address"] ?? "",
// "telepon" => $dataStiker["phone"] ?? "",
// "unit_kerja" => $dataStiker["depart"],
// "jenis_stiker" => $dataStiker["member"],
// "awal" => $awal,
// "akhir" => $akhir,
// "harga" => $dataStiker["harga"] ?? 0,
// "keterangan" => $dataStiker["note"] ?? "",
// "tanggal" => $dateNow,
// "operator" => $user->nomer,
// ];
// DB::table("transaksi_stiker")->insert($dataStiker);
// if ($dataVehicle) {
// foreach ($dataVehicle as $nopol) {
// $dataVehicle = [
// "notrans" => $notrans,
// "nopol" => $nopol["nopol"],
// "jenis_mobil" => $nopol["vehicle"],
// "jenis_member" => $nopol["member"],
// "merk" => $nopol["merk"],
// ];
// DB::table("detail_transaksi_stiker")->insert($dataVehicle);
// }
// } else {
// return 0;
// }
// if ($dataCard) {
// foreach ($dataCard as $cardNumber) {
// $dataCard = [
// "notrans" => $noid,
// "no_card" => $cardNumber["nocard"],
// "no_urut" => $cardNumber["nourut"],
// ];
// DB::table("card_member")->insert($dataCard);
// }
// } else {
// return 0;
// }
// DB::table("indexnum")
// ->where("nama", "sticker")
// ->update(["value" => DB::raw("lpad(to_number(value,'9999')+1)::text, 4, '0')")]);
// DB::table("indexnum")
// ->where("nama", "identitas")
// ->update(["value" => DB::raw("lpad(to_number(value,'9999')+1)::text, 4, '0')")]);
// $response = true;
// $msg = "Data berhasil di simpan";
// } catch (Exception $e) {
// $response = false;
// $msg = "Gagal menyimpan data " . $e->getMessage() . $e->getLine();
// }
return $createData;
// return response()->json([
// // "response" => $response,
// "response" => $createData,
// "pesan" => $createData,
// ]);
}
/**
@@ -247,8 +339,9 @@ public function store(Request $request)
public function show($id)
{
Log::info("Data yang diterima : " . json_encode($id));
$dataStiker = StikerTrans::with("stiker_details.vehicle", "memberCard")
->select(
$dataStiker = StikerTrans
// with("stiker_details.vehicle", "memberCard")
::select(
"transaksi_stiker.notrans",
"transaksi_stiker.nama",
"transaksi_stiker.alamat",
@@ -258,13 +351,31 @@ public function show($id)
"transaksi_stiker.jenis_langganan",
"transaksi_stiker.unit_kerja",
"transaksi_stiker.no_id",
"transaksi_stiker.harga",
"jl.nama as jmember",
"sk.nama as dept",
// "detail_transaksi_stiker.nama",
// "detail_transaksi_stiker.tarif",
)
->leftJoin("jenis_langganan as jl", "jl.id", "=", "transaksi_stiker.jenis_langganan")
->leftJoin("stasiunkerja as sk", "sk.kode", "=", "transaksi_stiker.unit_kerja")
->where("notrans", "like", "%" . $id . "%")
->first();
$vehicleDetails = StikerTransDetail::query()
->from("detail_transaksi_stiker as dts")
->select("jm.nama as vehicle", "ts.tarif", "dts.*")
->leftJoin("jenis_mobil as jm", "dts.jenis_mobil", "=", "jm.id")
->leftJoin("tarif_stiker as ts", function ($join) {
$join->on("dts.jenis_member", "=", "ts.jenis_langganan")->on("dts.jenis_mobil", "=", "ts.id_mobil");
})
->where("notrans", $dataStiker->notrans)
->get();
$cardMember = MemberCard::where("notrans", $dataStiker->no_id)->get();
$dataStiker["stiker_details"] = $vehicleDetails;
$dataStiker["memberCard"] = $cardMember;
// $dataStiker = DB::table('transaksi_stiker as ts')
// ->leftjoin('detail_transaksi_stiker as dts','dts.notrans','=','ts.notrans')
// ->leftJoin('jenis_langganan as jl','jl.id','=','ts.jenis_langganan')
@@ -4,6 +4,8 @@
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpFoundation\StreamedResponse;
class TransaksiParkirController extends Controller
{
@@ -17,6 +19,118 @@ public function index()
return view ('operasional.transaksi-parkir');
}
/**
* Data transaksi parkir dengan filter pencarian.
*
* Filter (semua opsional):
* - field: id | no_pol | no_barcode, + q: kata kunci
* - tgl_awal / tgl_akhir (Y-m-d): periode kolom tanggal
* - status_kendaraan: 1 = masuk, 0 = keluar
* - status_transaksi: 0 = casual, 3 = member, 32 = denda
* - export=csv: unduh hasil filter sebagai CSV
*/
public function data(Request $request)
{
$request->validate([
'field' => 'nullable|in:id,no_pol,no_barcode',
'q' => 'nullable|string|max:100',
'tgl_awal' => 'nullable|date_format:Y-m-d',
'tgl_akhir' => 'nullable|date_format:Y-m-d|after_or_equal:tgl_awal',
'status_kendaraan' => 'nullable|in:0,1',
'status_transaksi' => 'nullable|in:0,3,32',
'per_page' => 'nullable|integer|min:1|max:500',
]);
$query = $this->filteredQuery($request);
if ($request->query('export') === 'csv') {
return $this->exportCsv($query);
}
$perPage = (int) $request->input('per_page', 50);
$rows = $query->paginate($perPage);
return response()->json([
'status' => true,
'data' => $rows->items(),
'meta' => [
'current_page' => $rows->currentPage(),
'per_page' => $rows->perPage(),
'total' => $rows->total(),
'last_page' => $rows->lastPage(),
],
]);
}
protected function filteredQuery(Request $request)
{
$query = DB::table('transaksi_parkir as tp')
->leftJoin('jenis_mobil as jm', 'jm.id', '=', 'tp.id_kendaraan')
->select(
'tp.id',
'tp.no_pol',
'tp.no_barcode',
'tp.id_kendaraan',
'jm.nama as nama_kendaraan',
'tp.status',
'tp.status_transaksi',
'tp.waktu_masuk',
'tp.waktu_keluar',
'tp.tanggal'
);
// 1. Pencarian: id / no_pol / no_barcode
if ($request->filled('q')) {
$field = $request->input('field', 'id');
$query->where("tp.{$field}", 'ILIKE', '%' . $request->input('q') . '%');
}
// 2. Periode awal - akhir (kolom tanggal)
if ($request->filled('tgl_awal')) {
$query->whereDate('tp.tanggal', '>=', $request->input('tgl_awal'));
}
if ($request->filled('tgl_akhir')) {
$query->whereDate('tp.tanggal', '<=', $request->input('tgl_akhir'));
}
// 3. Status kendaraan: 1 = masuk, 0 = keluar
if ($request->filled('status_kendaraan')) {
$query->where('tp.status', (int) $request->input('status_kendaraan'));
}
// 4. Status transaksi: 0 = casual, 3 = member, 32 = denda
if ($request->filled('status_transaksi')) {
$query->where('tp.status_transaksi', (int) $request->input('status_transaksi'));
}
return $query->orderBy('tp.tanggal', 'desc');
}
protected function exportCsv($query): StreamedResponse
{
$filename = 'transaksi-parkir-' . now()->format('Ymd-His') . '.csv';
return response()->streamDownload(function () use ($query) {
$out = fopen('php://output', 'w');
fputcsv($out, ['ID', 'No. Pol', 'Barcode', 'Kendaraan', 'Status Kendaraan', 'Status Transaksi', 'Waktu Masuk', 'Waktu Keluar']);
$query->chunk(1000, function ($rows) use ($out) {
foreach ($rows as $r) {
fputcsv($out, [
$r->id,
$r->no_pol,
$r->no_barcode,
trim(($r->id_kendaraan ?? '') . ' ' . ($r->nama_kendaraan ?? '')),
$r->status,
$r->status_transaksi,
$r->waktu_masuk,
$r->waktu_keluar,
]);
}
});
fclose($out);
}, $filename, ['Content-Type' => 'text/csv']);
}
/**
* Show the form for creating a new resource.
*
@@ -1,999 +0,0 @@
<?php
namespace App\Http\Controllers\Parking;
use App\Http\Controllers\Controller;
use App\Models\TransaksiParkir;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class ReportTransaksiController extends Controller
{
public function byGate()
{
return view("report.by-gate");
}
// private function transaksiParkir($isMirror)
// {
// $mirror = DB::table('parkir_transaksi')
// $query = DB::table('transaksi_parkir');
// return $table;
// }
public function gateData(Request $request)
{
Log::info($request->all());
$awal = $request->tanggal_mulai;
$akhir = $request->tanggal_selesai;
$isShift = $request->shift === "on";
$dateBy = $isShift ? "pklogin" : "waktu_keluar";
$paymentMethod = $request->payment_method === "null" ? null : $request->payment_method;
// 🔹 Base select
$select = [
"np.nama as gate",
"tp.id_pintu_keluar",
"p.nama as petugas",
"tp.id_op_keluar",
"jm.nama as kendaraan",
"tp.status_transaksi",
DB::raw("count(*) as jumlah_transaksi"),
DB::raw("sum(tp.bayar_keluar) as income_transaksi"),
];
// 🔹 Tambah shift kalau aktif
if ($isShift) {
$select[] = "tp.id_shift_keluar";
}
// 🔹 Base group
$groupBy = ["tp.id_pintu_keluar", "np.nama", "p.nama", "tp.id_op_keluar", "jm.nama", "tp.status_transaksi"];
if ($isShift) {
$groupBy[] = "tp.id_shift_keluar";
}
$query = DB::table("transaksi_parkir as tp")
->leftJoin("pegawai as p", "p.nomer", "=", "tp.id_op_keluar")
->leftJoin("nama_pos as np", "np.id", "=", "tp.id_pintu_keluar")
->leftJoin("jenis_mobil as jm", "jm.id", "=", "tp.id_kendaraan")
->select($select)
// 🔹 Filter tanggal (dynamic field)
->where("tp.status", 0)
->whereBetween($dateBy, [$awal . " 00:00:00", $akhir . " 23:59:59"])
// // 🔹 STATUS
// ->when($request->status_transaksi, fn($q, $v) =>
// $q->where('tp.status_transaksi', $v)
// )
// 🔹 PINTU
->when($request->id_pintu_keluar, fn($q, $v) => $q->whereIn("tp.id_pintu_keluar", (array) $v))
// 🔹 Status Transaksi
// ->when($request->status_transaksi, function ($q) use ($request) {
->when(isset($request->status_transaksi), function ($q) use ($request) {
return match ($request->status_transaksi) {
// '0' => $q->whereNotIn('tp.status_transaksi', ['3','-1'])->whereNotNull('alasan'),
"0" => $q->where("tp.status_transaksi", "0")->whereNull("alasan"),
"3" => $q->where("tp.status_transaksi", "3")->whereNull("alasan"),
"-1" => $q->where("tp.status_transaksi", "-1")->whereNotNull("alasan"),
"null" => $q->whereNull("alasan"),
};
})
// 🔹 CARA BAYAR
->when($request->cara_bayar, function ($q) use ($request) {
return match ($request->cara_bayar) {
"cash" => $q->where("tp.cara_bayar", "!=", 3),
"cashless" => $q->where("tp.cara_bayar", 3),
default => $q,
};
})
->when($paymentMethod, function ($q) use ($paymentMethod) {
return $q->where("tp.rep_bank", $paymentMethod);
// return match ($request->payment_method) {
// 2 => $q->where('tp.rep_bank', 2),
// 3 => $q->where('tp.rep_bank', 3),
// 4 => $q->where('tp.rep_bank', 4),
// 5 => $q->where('tp.rep_bank', 5),
// 9 => $q->where('tp.rep_bank', 9),
// default => $q
// };
})
// // 🔹 PETUGAS
// ->when($request->id_op_keluar, fn($q, $v) =>
// $q->whereIn('tp.id_op_keluar', (array) $v)
// )
// // 🔹 KENDARAAN
// ->when($request->id_kendaraan, fn($q, $v) =>
// $q->whereIn('tp.id_kendaraan', (array) $v)
// )
->groupBy($groupBy)
->orderBy("tp.id_pintu_keluar")
->when($isShift, fn($q) => $q->orderBy("tp.id_shift_keluar"))
->orderBy("tp.id_op_keluar")
->orderBy("jm.nama")
->orderBy("tp.status_transaksi");
return response()->json([
"byGateData" => $query->get(),
]);
}
public function allReport()
{
return view("report.all-report");
}
public function allReportData(Request $request)
{
return response()->json([
"vehicle" => $this->vehicleReport($request),
"officer" => $this->reportOfficer($request),
"payment" => $this->paymentType($request),
]);
}
public function vehicleReport(Request $request)
{
Log::info("== allReportData START ==");
Log::info($request->harian);
Log::info($request->tanggal);
$selectDate = $request->tanggal;
$start = microtime(true);
$harian = $request->harian ?? 1;
$dateNow = "2026-01-25";
$startDate = $selectDate ? $selectDate : $dateNow;
$endDate = $selectDate ? $selectDate : $dateNow;
Log::info("Tanggal laporan", [
"start" => $startDate,
"end" => $endDate,
]);
// =========================
// QUERY UTAMA (LEFT JOIN)
// =========================
Log::info("Query transaksi dimulai");
// $dateColumn = $harian == 1
// ? 'tp.waktu_keluar'
// : 'tp.pklogin';
if ($harian == 1) {
$isReport = "harian";
// $dateColumn = ["tp.waktu_keluar"];
$dateColumn = ["tp.waktu_masuk", "tp.waktu_keluar"];
} else {
$isReport = "shift";
// $dateColumn = ["tp.pklogin"];
$dateColumn = ["tp.pmlogin", "tp.pklogin"];
}
$transactions = DB::table("transaksi_parkir as tp")
->leftJoin("jenis_mobil as jm", "jm.id", "=", "tp.id_kendaraan")
->leftJoin("pegawai as pm", "pm.nomer", "=", "tp.id_op_masuk")
->leftJoin("pegawai as pk", "pk.nomer", "=", "tp.id_op_keluar")
->where(function ($q) use ($dateColumn, $startDate, $endDate, $selectDate) {
$q->whereBetween($dateColumn[0], [$startDate . " 00:00:00", $endDate . " 23:59:59"])->orWhereBetween(
$dateColumn[0],
[$startDate . " 00:00:00", $endDate . " 23:59:59"],
);
// $q->whereRaw('DATE(tp.waktu_masuk) = ?', [$selectDate])
// ->orWhereRaw('DATE(tp.waktu_keluar) = ?', [$selectDate]);
})
// ->whereRaw("DATE($dateColumn) = ?", [$dateNow])
// ->whereBetween($dateColumn, [$startDate, $endDate])
->select(
"tp.id",
"jm.nama as jenis_mobil",
"tp.id_kendaraan",
"pm.nama as kasir_masuk",
"pk.nama as kasir_keluar",
"tp.id_op_masuk",
"tp.waktu_masuk",
"tp.waktu_keluar",
"tp.status",
"tp.bayar_masuk",
"tp.bayar_keluar",
"tp.status_transaksi",
"tp.cara_bayar",
)
->get();
Log::info("Query transaksi selesai", [
"rows" => $transactions->count(),
"query_time_ms" => round((microtime(true) - $start) * 1000, 2),
]);
// =========================
// REKAP PER KENDARAAN
// =========================
Log::info("Rekap kendaraan dimulai");
$tunai = $transactions
->sortBy("id_kendaraan")
->groupBy("id_kendaraan")
->map(function ($items, $idKendaraan) use ($startDate, $endDate) {
$start = $startDate . " 00:00:00";
$end = $endDate . " 00:00:00";
// ======================
// PISAH MASUK & KELUAR
// ======================
// $masuk = $items;
$masuk = $items->filter(fn($row) => in_array(data_get($row, "cara_bayar"), [null, 0, "0"], true));
// $masuk = $items->where("status", 1);
// ->whereBetween("waktu_masuk", [$startDate . " 00:00:00", $endDate . " 23:59:59"]);
// ->filter(function ($item) use ($start, $end) {
// $waktuMasuk = $item->waktu_masuk;
// $pmLogin = $item->pmlogin ?? null;
// $inWaktuMasuk = $waktuMasuk && $waktuMasuk >= $start && $waktuMasuk <= $end;
// $inPmMasuk = $pmLogin && $pmLogin >= $start && $pmLogin <= $end;
// return $inWaktuMasuk || $inPmMasuk;
// });
// $keluar = $items->where('status', 0);
$keluar = $items->filter(fn($r) => (int) $r->status === 0);
// $tunai = $keluar->where('cara_bayar', '!=', 3);
$tunai = $keluar->filter(fn($row) => in_array(data_get($row, "cara_bayar"), [null, 0, "0"], true));
// ======================
// MASUK
// ======================
$masukCasual = $masuk->where("status_transaksi", "0");
$masukMember = $masuk->where("status_transaksi", "3");
// ======================
// KELUAR
// ======================
// $keluarCasual = $tunai->where('status_transaksi', '0');
// $keluarMember = $tunai->where('status_transaksi', '3');
// $keluarLost = $tunai->where('status_transaksi', '32');
$keluarCasual = $tunai->filter(
fn($r) => (string) data_get($r, "status_transaksi") === "0" && data_get($r, "id_op_masuk") !== null,
);
$keluarMember = $tunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "3");
$keluarLost = $tunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "32");
$keluarManual = $tunai->filter(fn($r) => data_get($r, "id_op_masuk") === null);
$keluarMasalah = $tunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "-1");
// ======================
// PEMBAYARAN (KELUAR)
// ======================
// $nonTunai = $keluar->where('cara_bayar', 3);
// $tunai = $keluar->where('cara_bayar', '!=', 3);
$totalCash =
$keluarCasual->sum("bayar_keluar") +
$keluarLost->sum("bayar_keluar") +
$keluarMember->sum("bayar_keluar") +
$keluarManual->sum("bayar_keluar");
return [
"id_kendaraan" => $idKendaraan,
"jenis_mobil" => $items->first()->jenis_mobil,
"total" => $items->count(),
"tunaiOut" => $tunai->count(),
// ========= MASUK =========
"masuk" => [
"casual" => [
"jumlah" => $masuk->count(),
"income" => $masukCasual->sum("bayar_masuk"),
],
"member" => [
"jumlah" => $masukMember->count(),
"income" => $masukMember->sum("bayar_masuk"),
],
],
// ========= KELUAR =========
"keluar" => [
"casual" => [
"jumlah" => $keluarCasual->count(),
"income" => $keluarCasual->sum("bayar_keluar"),
],
"lost" => [
"jumlah" => $keluarLost->count(),
"income" => $keluarLost->sum("bayar_keluar"),
],
"member" => [
"jumlah" => $keluarMember->count(),
"income" => $keluarMember->sum("bayar_keluar"),
],
"manual" => [
"jumlah" => $keluarManual->count(),
"income" => $keluarManual->sum("bayar_keluar"),
],
"masalah" => [
"jumlah" => $keluarMasalah->count(),
"income" => $keluarMasalah->sum("bayar_keluar"),
],
],
"totalCash" => $totalCash,
];
})
->values();
$cashless = $transactions
->sortBy("id_kendaraan")
->groupBy("id_kendaraan")
->map(function ($items, $idKendaraan) {
// ======================
// PISAH MASUK & KELUAR
// ======================
// $masuk = $items->where("status", 1);
$masuk = $items->filter(fn($row) => in_array(data_get($row, "cara_bayar"), [null, 0, "0"], true));
// $keluar = $items->where('status', 0);
// $nonTunai = $keluar->where('cara_bayar', 3);
$keluar = $items->filter(fn($r) => (int) $r->status === 0);
// $tunai = $keluar->where('cara_bayar', '!=', 3);
$nonTunai = $keluar->filter(fn($row) => (string) data_get($row, "cara_bayar") === "3");
// ======================
// MASUK
// ======================
$masukCasual = $masuk;
$masukMember = $masuk->where("status_transaksi", "3");
// ======================
// KELUAR
// ======================
// $keluarCasual = $nonTunai->where('status_transaksi', '0');
// $keluarMember = $nonTunai->where('status_transaksi', '3');
// $keluarLost = $nonTunai->where('status_transaksi', '32');
$keluarCasual = $nonTunai->filter(
fn($r) => (string) data_get($r, "status_transaksi") === "0" && data_get($r, "id_op_masuk") !== null,
);
$keluarMember = $nonTunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "3");
$keluarLost = $nonTunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "32");
$keluarManual = $nonTunai->filter(fn($r) => data_get($r, "id_op_masuk") === null);
$totalCashless =
$keluarCasual->sum("bayar_keluar") +
$keluarLost->sum("bayar_keluar") +
$keluarMember->sum("bayar_keluar") +
$keluarManual->sum("bayar_keluar");
// ======================
// PEMBAYARAN (KELUAR)
// ======================
return [
"id_kendaraan" => $idKendaraan,
"jenis_mobil" => $items->first()->jenis_mobil,
"total" => $items->count(),
"nonTunaiOut" => $nonTunai->count(),
// ========= MASUK =========
"masuk" => [
"casual" => [
"jumlah" => $masuk->count(),
"income" => $masukCasual->sum("bayar_masuk"),
],
"member" => [
"jumlah" => $masukMember->count(),
"income" => $masukMember->sum("bayar_masuk"),
],
],
// ========= KELUAR =========
"keluar" => [
"casual" => [
"jumlah" => $keluarCasual->count(),
"income" => $keluarCasual->sum("bayar_keluar"),
],
"lost" => [
"jumlah" => $keluarLost->count(),
"income" => $keluarLost->sum("bayar_keluar"),
],
"member" => [
"jumlah" => $keluarMember->count(),
"income" => $keluarMember->sum("bayar_keluar"),
],
"manual" => [
"jumlah" => $keluarManual->count(),
"income" => $keluarManual->sum("bayar_keluar"),
],
],
"totalCashless" => $totalCashless,
];
})
->values();
$transactions = DB::table("transaksi_parkir as tp")
->leftJoin("jenis_mobil as jm", "jm.id", "=", "tp.id_kendaraan")
->leftJoin("pegawai as pm", "pm.nomer", "=", "tp.id_op_masuk")
->leftJoin("pegawai as pk", "pk.nomer", "=", "tp.id_op_keluar")
->whereDate("waktu_masuk", "<", $dateNow)
->select(
"tp.id_kendaraan",
"jm.nama as jenis_mobil",
DB::raw("SUM(CASE WHEN tp.status = 1 THEN 1 ELSE 0 END) as overnight"),
DB::raw("SUM(CASE WHEN tp.status = 3 THEN 1 ELSE 0 END) as onexpired"),
)
->groupBy("tp.id_kendaraan", "jm.nama")
->orderBy("tp.id_kendaraan")
// dd($transactions->toSql());
->get();
$onCheck = $transactions
// ->sortBy('id_kendaraan')
// ->groupBy('id_kendaraan')
->map(function ($items) {
// $overNight = $items->filter(fn ($r) => (int) $r->status === 1);
// $onExpired = $items->filter(fn ($r) => (int) $r->status === 3);
return [
"id_kendaraan" => $items->id_kendaraan,
"jenis_mobil" => $items->jenis_mobil,
"overNight" => (int) $items->overnight,
"onExpired" => (int) $items->onexpired,
];
})
->values();
Log::info("Rekap kendaraan selesai", [
"total_kendaraan" => $tunai->count(),
]);
Log::info("== allReportData END ==", [
"total_time_ms" => round((microtime(true) - $start) * 1000, 2),
]);
return [
// 'date' => $startDate->format('Y-m-d'),÷
"date" => $startDate,
"reportTime" => $isReport,
"tunai" => $tunai,
"nontunai" => $cashless,
"overNight" => $onCheck,
// 'transactions' => $transactions->groupBy('id_kendaraan')
];
}
// ==========###################### Begin Laporan per petugas #######################===============//
public function reportOfficer(Request $request)
{
Log::info("== Officer Report START ==");
Log::info($request->harian);
Log::info($request->tanggal);
$selectDate = $request->tanggal;
$start = microtime(true);
$harian = $request->harian ?? 1;
// $selectDate = Carbon::createFromFormat("", $tanggalRequest)->format("Y-m-d");
// $dateNow = Carbon::now()->format('Y-m-d');
$dateNow = "2026-01-25";
// $startDate = Carbon::today()->startOfDay();
// $endDate = Carbon::today()->endOfDay();
// $startDate = $selectDate . ' 00:00:00';
// $endDate = $selectDate . ' 23:59:59';
$startDate = $selectDate ? $selectDate : $dateNow;
$endDate = $selectDate ? $selectDate : $dateNow;
Log::info("Tanggal laporan", [
"start" => $startDate,
"end" => $endDate,
]);
// =========================
// QUERY UTAMA (LEFT JOIN)
// =========================
Log::info("Query transaksi dimulai");
// $dateColumn = $harian == 1
// ? 'tp.waktu_keluar'
// : 'tp.pklogin';
if ($harian == 1) {
$dateColumn = ["tp.waktu_masuk", "tp.waktu_keluar"];
$dateColumn = ["tp.waktu_keluar"];
} else {
$dateColumn = ["tp.pmlogin", "tp.pklogin"];
$dateColumn = ["tp.pklogin"];
}
$transactions = DB::table("transaksi_parkir as tp")
->leftJoin("pegawai as p", "p.nomer", "=", "tp.id_op_keluar")
// ->leftJoin("pegawai as pm", "pm.nomer", "=", "tp.id_op_masuk")
// ->leftJoin("pegawai as pk", "pk.nomer", "=", "tp.id_op_keluar")
->where(function ($q) use ($dateColumn, $startDate, $endDate, $selectDate) {
$q->whereBetween($dateColumn[0], [$startDate . " 00:00:00", $endDate . " 23:59:59"])->orWhereBetween(
$dateColumn[0],
[$startDate . " 00:00:00", $endDate . " 23:59:59"],
);
// $q->whereRaw('DATE(tp.waktu_masuk) = ?', [$selectDate])
// ->orWhereRaw('DATE(tp.waktu_keluar) = ?', [$selectDate]);
})
// ->whereNull("id_op_masuk")
// ->where("manual", 1)
// ->whereRaw("DATE($dateColumn) = ?", [$dateNow])
// ->whereBetween($dateColumn, [$startDate, $endDate])
->select(
"p.nomer as id_petugas",
"p.nama as petugas",
// "tp.id_kendaraan",
// "pm.nama as kasir_masuk",
// "pk.nama as kasir_keluar",
"tp.id_op_masuk", // ✅ ADA
"tp.id_op_keluar",
"tp.waktu_masuk",
"tp.waktu_keluar",
"tp.status",
"tp.bayar_masuk",
"tp.bayar_keluar",
"tp.status_transaksi",
"tp.cara_bayar",
)
->get();
// Log::info("Result nya : " . json_encode($transactions));
Log::info("Query transaksi selesai", [
"rows" => $transactions->count(),
"query_time_ms" => round((microtime(true) - $start) * 1000, 2),
]);
// =========================
// REKAP PER KENDARAAN
// =========================
Log::info("Rekap petugas dimulai");
$tunai = $transactions
->sortBy("petugas")
->groupBy("id_op_keluar")
->map(function ($items, $id_op_keluar) {
// ======================
// PISAH MASUK & KELUAR
// ======================
// $masuk = $items->where('status', 1);
$masuk = $items->filter(fn($row) => in_array(data_get($row, "cara_bayar"), [null, 0, "0"], true));
// $keluar = $items->where('status', 0);
$keluar = $items->filter(fn($r) => (int) $r->status === 0);
// $tunai = $keluar->where('cara_bayar', '!=', 3);
// ======================
// MASUK
// ======================
// $masukCasual = $masuk->where('status_transaksi', '0');
// $masukMember = $masuk->where('status_transaksi', '3');
// ======================
// KELUAR
// ======================
// $keluarCasual = $tunai->where('status_transaksi', '0');
// $keluarMember = $tunai->where('status_transaksi', '3');
// $keluarLost = $tunai->where('status_transaksi', '32');
$tunai = $keluar->filter(fn($row) => in_array(data_get($row, "cara_bayar"), [null, 0, "0"], true));
$keluarCasual = $tunai->filter(
fn($r) => (string) data_get($r, "status_transaksi") === "0" && data_get($r, "id_op_masuk") !== null,
);
$keluarMember = $tunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "3");
$keluarLost = $tunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "32");
$keluarManual = $tunai->filter(fn($r) => data_get($r, "id_op_masuk") === null);
$keluarMasalah = $tunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "-1");
// ======================
// PEMBAYARAN (KELUAR)
// ======================
// $nonTunai = $keluar->where('cara_bayar', 3);
// $tunai = $keluar->where('cara_bayar', '!=', 3);
$totalCash =
$keluarCasual->sum("bayar_keluar") +
$keluarLost->sum("bayar_keluar") +
$keluarMember->sum("bayar_keluar") +
$keluarManual->sum("bayar_keluar");
Log::info("Transaksi tunai : ");
return [
"id_petugas" => $id_op_keluar,
"petugas" => $items->first()->petugas,
"total" => 0,
"tunaiOut" => $tunai->count(),
// ========= MASUK =========
"masuk" => [
"casual" => [
"jumlah" => 0,
"income" => $masuk->sum("bayar_masuk"),
],
// 'member' => [
// 'jumlah' => $masukMember->count(),
// 'income' => $masukMember->sum('bayar_masuk'),
// ],
],
// ========= KELUAR =========
"keluar" => [
"casual" => [
"jumlah" => $keluarCasual->count(),
"income" => $keluarCasual->sum("bayar_keluar"),
],
"lost" => [
"jumlah" => $keluarLost->count(),
"income" => $keluarLost->sum("bayar_keluar"),
],
"member" => [
"jumlah" => $keluarMember->count(),
"income" => $keluarMember->sum("bayar_keluar"),
],
"manual" => [
"jumlah" => $keluarManual->count(),
"income" => $keluarManual->sum("bayar_keluar"),
],
"masalah" => [
"jumlah" => $keluarMasalah->count(),
"income" => $keluarMasalah->sum("bayar_keluar"),
],
],
"totalCash" => $totalCash,
];
})
->values();
$cashless = $transactions
->sortBy("id_op_keluar")
->groupBy("id_op_keluar")
->map(function ($items, $id_op_keluar) {
// ======================
// PISAH MASUK & KELUAR
// ======================
// $masuk = $items->where('status', 1);
$masuk = $items->filter(fn($row) => in_array(data_get($row, "cara_bayar"), [null, 0, "0"], true));
// $keluar = $items->where('status', 0);
// $nonTunai = $keluar->where('cara_bayar', 3);
$keluar = $items->filter(fn($r) => (int) $r->status === 0);
// $tunai = $keluar->where('cara_bayar', '!=', 3);
$nonTunai = $keluar->filter(fn($row) => (string) data_get($row, "cara_bayar") === "3");
// ======================
// MASUK
// ======================
// $masukCasual = $masuk->where('status_transaksi', '0');
// $masukMember = $masuk->where('status_transaksi', '3');
// ======================
// KELUAR
// ======================
// $keluarCasual = $nonTunai->where('status_transaksi', '0');
// $keluarMember = $nonTunai->where('status_transaksi', '3');
// $keluarLost = $nonTunai->where('status_transaksi', '32');
$keluarCasual = $nonTunai->filter(
fn($r) => (string) data_get($r, "status_transaksi") === "0" && data_get($r, "id_op_masuk") !== null,
);
$keluarMember = $nonTunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "3");
$keluarLost = $nonTunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "32");
$keluarManual = $nonTunai->filter(fn($r) => data_get($r, "id_op_masuk") === null);
$totalCashless =
$keluarCasual->sum("bayar_keluar") +
$keluarLost->sum("bayar_keluar") +
$keluarMember->sum("bayar_keluar") +
$keluarManual->sum("bayar_keluar");
// ======================
// PEMBAYARAN (KELUAR)
// ======================
return [
"id_petugas" => $id_op_keluar,
"petugas" => $items->first()->petugas,
"total" => 0,
"nonTunaiOut" => $nonTunai->count(),
// ========= MASUK =========
"masuk" => [
"casual" => [
"jumlah" => 0,
"income" => $masuk->sum("bayar_masuk"),
],
"member" => [
"jumlah" => $masuk->count(),
"income" => $masuk->sum("bayar_masuk"),
],
],
// ========= KELUAR =========
"keluar" => [
"casual" => [
"jumlah" => $keluarCasual->count(),
"income" => $keluarCasual->sum("bayar_keluar"),
],
"lost" => [
"jumlah" => $keluarLost->count(),
"income" => $keluarLost->sum("bayar_keluar"),
],
"member" => [
"jumlah" => $keluarMember->count(),
"income" => $keluarMember->sum("bayar_keluar"),
],
"manual" => [
"jumlah" => $keluarManual->count(),
"income" => $keluarManual->sum("bayar_keluar"),
],
],
"totalCashless" => $totalCashless,
];
})
->values();
// $transactions = DB::table("transaksi_parkir as tp")
// ->leftJoin("pegawai as p", "p.nomer", "=", "tp.id_op_keluar")
// ->leftJoin("pegawai as pm", "pm.nomer", "=", "tp.id_op_masuk")
// ->leftJoin("pegawai as pk", "pk.nomer", "=", "tp.id_op_keluar")
// ->whereDate("waktu_masuk", "<", $dateNow)
// ->select(
// "tp.id_kendaraan",
// "p.nama as jenis_mobil",
// "tp.id_op_keluar",
// "p.nama as petugas",
// DB::raw("SUM(CASE WHEN tp.status = 1 THEN 1 ELSE 0 END) as overnight"),
// DB::raw("SUM(CASE WHEN tp.status = 3 THEN 1 ELSE 0 END) as onexpired"),
// )
// ->groupBy("tp.id_op_keluar", "petugas")
// ->orderBy("tp.id_op_keluar", "asc")
// ->groupBy("p.nomer", "p.nama")
// ->orderBy("p.nomer")
// dd($transactions->toSql());
// ->get();
// $onCheck = $transactions
// ->sortBy('id_kendaraan')
// ->groupBy('id_kendaraan')
// ->map(function ($items) {
// $overNight = $items->filter(fn ($r) => (int) $r->status === 1);
// $onExpired = $items->filter(fn ($r) => (int) $r->status === 3);
// return [
// "id_petugas" => $items->id_op_keluar,
// "petugas" => $items->petugas,
// "overNight" => (int) $items->overnight,
// "onExpired" => (int) $items->onexpired,
// ];
// })
// ->values();
Log::info("Rekap kendaraan selesai", [
"total_kendaraan" => $tunai->count(),
]);
Log::info("== allReportData END ==", [
"total_time_ms" => round((microtime(true) - $start) * 1000, 2),
]);
return [
"date" => $startDate,
"tunai" => $tunai,
"nontunai" => $cashless,
];
}
// ========== End Laporan per petugas ===============///
// ==========###################### Begin Laporan per petugas #######################===============//
public function paymentType(Request $request)
{
Log::info("== Officer Report START ==");
Log::info($request->harian);
Log::info($request->tanggal);
$selectDate = $request->tanggal;
$start = microtime(true);
$harian = $request->harian ?? 1;
$dateNow = "2026-01-25";
$startDate = $selectDate ? $selectDate : $dateNow;
$endDate = $selectDate ? $selectDate : $dateNow;
Log::info("Tanggal laporan", [
"start" => $startDate,
"end" => $endDate,
]);
// ============= QUERY UTAMA (LEFT JOIN) =========================
Log::info("Query transaksi dimulai");
if ($harian == 1) {
// $dateColumn = ["tp.waktu_keluar"];
$dateColumn = ["tp.waktu_masuk", "tp.waktu_keluar"];
} else {
// $dateColumn = ["tp.pklogin"];
$dateColumn = ["tp.pmlogin", "tp.pklogin"];
}
$transactions = DB::table("transaksi_parkir as tp")
// ->leftJoin("pegawai as p", "p.nomer", "=", "tp.id_op_keluar")
// ->leftJoin("pegawai as pm", "pm.nomer", "=", "tp.id_op_masuk")
// ->leftJoin("pegawai as pk", "pk.nomer", "=", "tp.id_op_keluar")
->where(function ($q) use ($dateColumn, $startDate, $endDate, $selectDate) {
$q->whereBetween($dateColumn[0], [$startDate . " 00:00:00", $endDate . " 23:59:59"])->orWhereBetween(
$dateColumn[0],
[$startDate . " 00:00:00", $endDate . " 23:59:59"],
);
// $q->whereRaw('DATE(tp.waktu_masuk) = ?', [$selectDate])
// ->orWhereRaw('DATE(tp.waktu_keluar) = ?', [$selectDate]);
})
// ->whereRaw("DATE($dateColumn) = ?", [$dateNow])
// ->whereBetween($dateColumn, [$startDate, $endDate])
->select(
"rep_bank as bankType",
// "p.nomer as id_petugas",
// "p.nama as petugas",
// "tp.id_kendaraan",
// "pm.nama as kasir_masuk",
// "pk.nama as kasir_keluar",
"tp.id_op_masuk", // ✅ ADA
"tp.id_op_keluar",
"tp.waktu_masuk",
"tp.waktu_keluar",
"tp.status",
"tp.bayar_masuk",
"tp.bayar_keluar",
"tp.status_transaksi",
"tp.cara_bayar",
)
->get();
Log::info("Query transaksi selesai", [
"rows" => $transactions->count(),
"query_time_ms" => round((microtime(true) - $start) * 1000, 2),
]);
Log::info("Rekap payment dimulai");
$tunai = $transactions
->sortBy("bankType")
->groupBy("bankType")
->map(function ($items, $bankType) {
$keluar = $items->filter(fn($r) => (int) $r->status === 0);
$tunai = $keluar->filter(fn($row) => in_array(data_get($row, "cara_bayar"), [null, 0, "0"], true));
$keluarCasual = $tunai->filter(
fn($r) => (string) data_get($r, "status_transaksi") === "0" && data_get($r, "id_op_masuk") !== null,
);
$keluarMember = $tunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "3");
$keluarLost = $tunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "32");
$keluarManual = $tunai->filter(fn($r) => data_get($r, "id_op_masuk") === null);
$keluarMasalah = $tunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "-1");
$totalCash =
$keluarCasual->sum("bayar_keluar") +
$keluarLost->sum("bayar_keluar") +
$keluarMember->sum("bayar_keluar") +
$keluarManual->sum("bayar_keluar");
Log::info("Transaksi tunai : ");
return [
"typePayment" => $bankType,
"petugas" => $items->first()->bankType,
"total" => $items->count(),
"tunaiOut" => $tunai->count(),
"masuk" => [
"casual" => [
"jumlah" => 0,
"income" => 0,
],
],
"keluar" => [
"casual" => [
"jumlah" => $keluarCasual->count(),
"income" => $keluarCasual->sum("bayar_keluar"),
],
"lost" => [
"jumlah" => $keluarLost->count(),
"income" => $keluarLost->sum("bayar_keluar"),
],
"member" => [
"jumlah" => $keluarMember->count(),
"income" => $keluarMember->sum("bayar_keluar"),
],
"manual" => [
"jumlah" => $keluarManual->count(),
"income" => $keluarManual->sum("bayar_keluar"),
],
"masalah" => [
"jumlah" => $keluarMasalah->count(),
"income" => $keluarMasalah->sum("bayar_keluar"),
],
],
"totalCash" => $totalCash,
];
})
->values();
$cashless = $transactions
->sortBy("bankType")
->groupBy("bankType")
->map(function ($items, $bankType) {
$keluar = $items->filter(fn($r) => (int) $r->status === 0);
$nonTunai = $keluar->filter(fn($row) => (string) data_get($row, "cara_bayar") === "3");
$keluarCasual = $nonTunai->filter(
fn($r) => (string) data_get($r, "status_transaksi") === "0" && data_get($r, "id_op_masuk") !== null,
);
$keluarMember = $nonTunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "3");
$keluarLost = $nonTunai->filter(fn($r) => (string) data_get($r, "status_transaksi") === "32");
$keluarManual = $nonTunai->filter(fn($r) => data_get($r, "id_op_masuk") === null);
$totalCashless =
$keluarCasual->sum("bayar_keluar") +
$keluarLost->sum("bayar_keluar") +
$keluarMember->sum("bayar_keluar") +
$keluarManual->sum("bayar_keluar");
// ======================
// PEMBAYARAN (KELUAR)
// ======================
return [
"typePayment" => $bankType,
"petugas" => $items->first()->bankType,
"total" => $items->count(),
"nonTunaiOut" => $nonTunai->count(),
"masuk" => [
"casual" => [
"jumlah" => 0,
"income" => 0,
],
],
"keluar" => [
"casual" => [
"jumlah" => $keluarCasual->count(),
"income" => $keluarCasual->sum("bayar_keluar"),
],
"lost" => [
"jumlah" => $keluarLost->count(),
"income" => $keluarLost->sum("bayar_keluar"),
],
"member" => [
"jumlah" => $keluarMember->count(),
"income" => $keluarMember->sum("bayar_keluar"),
],
"manual" => [
"jumlah" => $keluarManual->count(),
"income" => $keluarManual->sum("bayar_keluar"),
],
],
"totalCashless" => $totalCashless,
];
})
->values();
Log::info("Rekap kendaraan selesai", [
"total_kendaraan" => $tunai->count(),
]);
Log::info("== allReportData END ==", [
"total_time_ms" => round((microtime(true) - $start) * 1000, 2),
]);
return [
"date" => $startDate,
"tunai" => $tunai,
"nontunai" => $cashless,
];
}
// ========== End Laporan payment Type ===============///
}
@@ -0,0 +1,185 @@
<?php
namespace App\Http\Controllers\Parking\Setup\Gate;
use App\Http\Controllers\Controller;
use App\Services\Parking\NetworkService;
use App\Models\ConfigPosNetwork;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
class MasterConfigNetworkController extends Controller
{
public function index(NetworkService $dhcp)
{
$reservations = ConfigPosNetwork::orderBy("hostname")->get();
$dhcpStatus = $dhcp->status();
return view("setup.index", compact("reservations", "dhcpStatus"));
}
public function status(NetworkService $dhcp)
{
return response()->json($dhcp->status());
}
public function store(Request $request)
{
$data = $request->validate([
"hostname" => ["required", "string", "max:100", 'regex:/^[a-zA-Z0-9_-]+$/'],
"mac_address" => ["required", "string", 'regex:/^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$/'],
"ip_address" => ["required", "ip"],
"enabled" => ["nullable", "boolean"],
]);
$data["mac_address"] = strtoupper($data["mac_address"]);
$data["enabled"] = $request->boolean("enabled");
// Cek duplicate MAC
if (ConfigPosNetwork::where("mac_address", $data["mac_address"])->exists()) {
return response()->json(
[
"success" => false,
"message" => "MAC address already registered.",
],
422,
);
}
// Cek duplicate IP
if (ConfigPosNetwork::where("ip_address", $data["ip_address"])->exists()) {
return response()->json(
[
"success" => false,
"message" => "IP address already registered.",
],
422,
);
}
$reservation = ConfigPosNetwork::create($data);
return response()->json(
[
"success" => true,
"message" => "DHCP reservation created successfully.",
"data" => $reservation,
],
201,
);
}
public function update(Request $request, ConfigPosNetwork $reservation)
{
$data = $request->validate([
"hostname" => ["required", "string", "max:100", 'regex:/^[a-zA-Z0-9_-]+$/'],
"mac_address" => ["required", "string", 'regex:/^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$/'],
"ip_address" => ["required", "ip"],
"enabled" => ["nullable", "boolean"],
]);
$data["mac_address"] = strtoupper($data["mac_address"]);
$data["enabled"] = $request->boolean("enabled");
// Cek duplicate MAC,
// kecuali reservation yang sedang diedit
if (
ConfigPosNetwork::where("mac_address", $data["mac_address"])->where("id", "!=", $reservation->id)->exists()
) {
return response()->json(
[
"success" => false,
"message" => "MAC address already registered.",
],
422,
);
}
// Cek duplicate IP
if (ConfigPosNetwork::where("ip_address", $data["ip_address"])->where("id", "!=", $reservation->id)->exists()) {
return response()->json(
[
"success" => false,
"message" => "IP address already registered.",
],
422,
);
}
$reservation->update($data);
return response()->json([
"success" => true,
"message" => "DHCP reservation updated successfully.",
"data" => $reservation->fresh(),
]);
}
public function destroy(ConfigPosNetwork $reservation)
{
$reservation->delete();
return response()->json([
"success" => true,
"message" => "Reservation deleted successfully.",
]);
}
public function test(NetworkService $dhcp)
{
try {
$result = $dhcp->test();
return response()->json([
"success" => true,
"message" => "DHCP configuration is valid.",
"data" => $result,
]);
} catch (\Throwable $e) {
return response()->json(
[
"success" => false,
"message" => $e->getMessage(),
],
422,
);
}
}
public function apply(NetworkService $dhcp)
{
try {
$dhcp->apply();
return response()->json([
"success" => true,
"message" => "DHCP configuration applied successfully.",
]);
} catch (\Throwable $e) {
return response()->json(
[
"success" => false,
"message" => $e->getMessage(),
],
500,
);
}
}
public function logs(NetworkService $dhcp)
{
return response()->json([
"success" => true,
"logs" => $dhcp->logs(30),
]);
}
}
@@ -5,7 +5,6 @@
use App\Http\Controllers\Controller;
use App\Models\ConfigHardware;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class MasterGateHardwareController extends Controller
{
@@ -48,9 +47,11 @@ public function store(Request $request)
*/
public function show($id)
{
Log::info($id);
// # ambil konfigurasi hardware berdasarkan id_pos (id pintu)
$gateDetails = ConfigHardware::where('id_pos', $id)->first();
return $gateDetails;
// # kalau belum ada datanya, kembalikan objek kosong biar form tetap kebuka
return response()->json($gateDetails ?? (object) []);
}
/**
@@ -73,8 +74,26 @@ public function edit($id)
*/
public function update(Request $request, $id)
{
Log::info($id);
Log::info($request->all());
// # hanya kolom yang terdaftar di fillable yang boleh disimpan
$allowed = (new ConfigHardware())->getFillable();
$data = $request->input('data', []);
$filtered = [];
foreach ((array) $data as $key => $value) {
if (in_array($key, $allowed, true)) {
// # ubah string kosong jadi null biar cocok dengan kolom integer
$filtered[$key] = $value === '' ? null : $value;
}
}
// # simpan (buat baru kalau id_pos belum punya baris hardware)
$hardware = ConfigHardware::updateOrCreate(['id_pos' => $id], $filtered);
return response()->json([
'success' => true,
'message' => 'Konfigurasi hardware berhasil disimpan.',
'data' => $hardware,
]);
}
/**
@@ -0,0 +1,206 @@
<?php
namespace App\Http\Controllers\Parking\Setup\Member;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use App\Models\Origin\MasterTarifStiker;
use App\Models\Origin\MasterStasiunKerja;
use App\Models\Origin\MasterJenisLangganan;
class MasterTarifStikerController extends Controller
{
/**
* Halaman utama. Data dropdown (stasiun kerja & jenis langganan)
* di-load langsung di sini biar gak perlu request tambahan pas modal dibuka.
*/
public function index()
{
$stasiunKerjaList = MasterStasiunKerja::orderBy("kode")->get(["kode", "nama"]);
$jenisLanggananList = MasterJenisLangganan::orderBy("id")->get(["id", "nama"]);
return view("Parking.setup.member.tarifstiker", compact("stasiunKerjaList", "jenisLanggananList"));
}
/**
* Ambil seluruh data tarif stiker, sekalian join nama stasiun kerja
* biar gak perlu N+1 query pas render tabel.
*/
public function data(): JsonResponse
{
$data = MasterTarifStiker::with(["stasiunKerja:kode,nama"])
->orderByDesc("tarif_id")
->get();
return response()->json([
"success" => true,
"data" => $data,
]);
}
/**
* Ambil satu data berdasarkan tarif_id.
*/
public function show(string $tarif_id): JsonResponse
{
$item = MasterTarifStiker::with(["stasiunKerja:kode,nama"])->find($tarif_id);
if (!$item) {
return response()->json(
[
"success" => false,
"message" => "Data tarif stiker tidak ditemukan.",
],
404,
);
}
return response()->json([
"success" => true,
"data" => $item,
]);
}
/**
* Simpan data tarif stiker baru.
* tarif_id tidak wajib diisi dari form — kalau kosong, di-generate
* otomatis (max(tarif_id) + 1) karena tabel ini tidak punya
* auto-increment/serial asli di database.
*/
public function store(Request $request): JsonResponse
{
$validator = Validator::make($request->all(), [
"tarif_id" => ["nullable", "integer", "unique:tarif_stiker,tarif_id"],
"id_mobil" => ["required", "string", "max:10"],
"jenis_langganan" => ["required", "string", "max:50"],
"tarif" => ["required", "numeric"],
"last_member" => ["nullable", "string", "max:5"],
"tgl_akhir" => ["nullable", "string", "max:2"],
"jenis" => ["nullable", "string", "max:30"],
"jenis_member" => ["nullable", "string", "max:50"],
"hitung_awal_bulan" => ["nullable", "integer"],
"available_online" => ["nullable", "boolean"],
"langganan" => ["nullable", "string", "max:32", "exists:stasiunkerja,kode"],
"kode_akses" => ["nullable", "string", "max:5"],
]);
if ($validator->fails()) {
return response()->json(
[
"success" => false,
"message" => "Validasi gagal.",
"errors" => $validator->errors(),
],
422,
);
}
$payload = $validator->validated();
$payload["available_online"] = $request->boolean("available_online");
// Transaksi + lock untuk menghindari race condition kalau
// ada 2 request bersamaan sama-sama generate tarif_id baru.
$item = DB::transaction(function () use ($payload) {
if (empty($payload["tarif_id"])) {
$maxId = MasterTarifStiker::lockForUpdate()->max("tarif_id");
$payload["tarif_id"] = ($maxId ?? 0) + 1;
}
return MasterTarifStiker::create($payload);
});
$item->load("stasiunKerja:kode,nama");
return response()->json(
[
"success" => true,
"message" => "Data tarif stiker berhasil ditambahkan.",
"data" => $item,
],
201,
);
}
/**
* Perbarui data tarif stiker berdasarkan tarif_id.
*/
public function update(Request $request, string $tarif_id): JsonResponse
{
$item = MasterTarifStiker::find($tarif_id);
if (!$item) {
return response()->json(
[
"success" => false,
"message" => "Data tarif stiker tidak ditemukan.",
],
404,
);
}
$validator = Validator::make($request->all(), [
"id_mobil" => ["required", "string", "max:10"],
"jenis_langganan" => ["required", "string", "max:50"],
"tarif" => ["required", "numeric"],
"last_member" => ["nullable", "string", "max:5"],
"tgl_akhir" => ["nullable", "string", "max:2"],
"jenis" => ["nullable", "string", "max:30"],
"jenis_member" => ["nullable", "string", "max:50"],
"hitung_awal_bulan" => ["nullable", "integer"],
"available_online" => ["nullable", "boolean"],
"langganan" => ["nullable", "string", "max:32", "exists:stasiunkerja,kode"],
"kode_akses" => ["nullable", "string", "max:5"],
]);
if ($validator->fails()) {
return response()->json(
[
"success" => false,
"message" => "Validasi gagal.",
"errors" => $validator->errors(),
],
422,
);
}
$payload = $validator->validated();
$payload["available_online"] = $request->boolean("available_online");
$item->update($payload);
$item->load("stasiunKerja:kode,nama");
return response()->json([
"success" => true,
"message" => "Data tarif stiker berhasil diperbarui.",
"data" => $item,
]);
}
/**
* Hapus data tarif stiker berdasarkan tarif_id.
*/
public function destroy(string $tarif_id): JsonResponse
{
$item = MasterTarifStiker::find($tarif_id);
if (!$item) {
return response()->json(
[
"success" => false,
"message" => "Data tarif stiker tidak ditemukan.",
],
404,
);
}
$item->delete();
return response()->json([
"success" => true,
"message" => "Data tarif stiker berhasil dihapus.",
]);
}
}
@@ -0,0 +1,177 @@
<?php
namespace App\Http\Controllers\Parking\Setup\Member;
use App\Http\Controllers\Controller;
use App\Models\Origin\MasterStasiunKerja;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class StasiunKerjaController extends Controller
{
/**
* Tampilkan halaman utama (view + tabel kosong, data di-load via JS).
*/
public function index()
{
return view("Parking.setup.member.stasiunkerja");
}
/**
* Ambil seluruh data stasiun kerja dalam format JSON.
* Dipanggil via fetch() dari JS untuk mengisi tabel.
*/
public function data(): JsonResponse
{
$data = MasterStasiunKerja::orderBy("kode")->get();
return response()->json([
"success" => true,
"data" => $data,
]);
}
/**
* Ambil satu data berdasarkan kode (dipakai saat buka form edit).
*/
public function show(string $kode): JsonResponse
{
$item = MasterStasiunKerja::find($kode);
if (!$item) {
return response()->json(
[
"success" => false,
"message" => "Data stasiun kerja tidak ditemukan.",
],
404,
);
}
return response()->json([
"success" => true,
"data" => $item,
]);
}
/**
* Simpan data stasiun kerja baru.
*/
public function store(Request $request): JsonResponse
{
$validator = Validator::make($request->all(), [
"kode" => ["required", "string", "max:50", Rule::unique("stasiunkerja", "kode")],
"nama" => ["required", "string", "max:255"],
"margin" => ["nullable", "integer"],
"spv" => ["nullable", "string", "max:20"],
"target" => ["nullable", "integer"],
"id_lokasi" => ["nullable", "string", "max:10"],
"lantai" => ["nullable", "string", "max:4"],
"status" => ["nullable", "integer"],
"alamat" => ["nullable", "string", "max:255"],
"bank" => ["nullable", "string", "max:64"],
"telp" => ["nullable", "string", "max:32"],
"kode_akses" => ["nullable", "string", "max:5"],
]);
if ($validator->fails()) {
return response()->json(
[
"success" => false,
"message" => "Validasi gagal.",
"errors" => $validator->errors(),
],
422,
);
}
$item = MasterStasiunKerja::create($validator->validated());
return response()->json(
[
"success" => true,
"message" => "Data stasiun kerja berhasil ditambahkan.",
"data" => $item,
],
201,
);
}
/**
* Perbarui data stasiun kerja berdasarkan kode.
*/
public function update(Request $request, string $kode): JsonResponse
{
$item = MasterStasiunKerja::find($kode);
if (!$item) {
return response()->json(
[
"success" => false,
"message" => "Data stasiun kerja tidak ditemukan.",
],
404,
);
}
$validator = Validator::make($request->all(), [
"nama" => ["required", "string", "max:255"],
"margin" => ["nullable", "integer"],
"spv" => ["nullable", "string", "max:20"],
"target" => ["nullable", "integer"],
"id_lokasi" => ["nullable", "string", "max:10"],
"lantai" => ["nullable", "string", "max:4"],
"status" => ["nullable", "integer"],
"alamat" => ["nullable", "string", "max:255"],
"bank" => ["nullable", "string", "max:64"],
"telp" => ["nullable", "string", "max:32"],
"kode_akses" => ["nullable", "string", "max:5"],
]);
if ($validator->fails()) {
return response()->json(
[
"success" => false,
"message" => "Validasi gagal.",
"errors" => $validator->errors(),
],
422,
);
}
$item->update($validator->validated());
return response()->json([
"success" => true,
"message" => "Data stasiun kerja berhasil diperbarui.",
"data" => $item,
]);
}
/**
* Hapus data stasiun kerja berdasarkan kode.
*/
public function destroy(string $kode): JsonResponse
{
$item = MasterStasiunKerja::find($kode);
if (!$item) {
return response()->json(
[
"success" => false,
"message" => "Data stasiun kerja tidak ditemukan.",
],
404,
);
}
$item->delete();
return response()->json([
"success" => true,
"message" => "Data stasiun kerja berhasil dihapus.",
]);
}
}
@@ -4,7 +4,9 @@
use App\Http\Controllers\Controller;
use App\Models\VehicleItem;
use App\Models\MasterTarif;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class MasterVehicleController extends Controller
{
@@ -40,7 +42,38 @@ public function create()
*/
public function store(Request $request)
{
//
$request->merge([
'id' => strtoupper((string) $request->input('id')),
'short_cut' => strtoupper((string) $request->input('short_cut')),
]);
$data = $request->validate([
'id' => 'required|string|max:16|alpha_num|unique:jenis_mobil,id',
'nama' => 'required|string|max:100',
'short_cut' => 'required|string|max:16',
'tarif' => 'required|numeric|min:0',
]);
$vehicle = DB::transaction(function () use ($data) {
$vehicle = VehicleItem::create([
'id' => strtoupper($data['id']),
'nama' => $data['nama'],
'short_cut' => strtoupper($data['short_cut']),
'tanggal' => now(),
'orang' => 0,
'validasi' => 0,
]);
MasterTarif::create([
'id_mobil' => $vehicle->id,
'tarif' => $data['tarif'],
'tanggal' => now(),
]);
return $vehicle;
});
return response()->json(['success' => true, 'message' => 'Jenis kendaraan dan tarif awal berhasil ditambahkan.', 'data' => $vehicle], 201);
}
/**
@@ -51,7 +84,10 @@ public function store(Request $request)
*/
public function show($id)
{
//
$vehicle = VehicleItem::findOrFail($id);
$tarif = MasterTarif::find($id);
return response()->json(['success' => true, 'data' => $vehicle, 'tarif' => $tarif]);
}
/**
@@ -62,7 +98,7 @@ public function show($id)
*/
public function edit($id)
{
//
return response()->json(['success' => true, 'data' => VehicleItem::findOrFail($id)]);
}
/**
@@ -74,7 +110,18 @@ public function edit($id)
*/
public function update(Request $request, $id)
{
//
$data = $request->validate([
'nama' => 'required|string|max:100',
'short_cut' => 'required|string|max:16',
]);
$vehicle = VehicleItem::findOrFail($id);
$vehicle->update([
'nama' => $data['nama'],
'short_cut' => strtoupper($data['short_cut']),
]);
return response()->json(['success' => true, 'message' => 'Jenis kendaraan berhasil diperbarui.', 'data' => $vehicle]);
}
/**
@@ -85,6 +132,11 @@ public function update(Request $request, $id)
*/
public function destroy($id)
{
//
DB::transaction(function () use ($id) {
MasterTarif::where('id_mobil', $id)->delete();
VehicleItem::findOrFail($id)->delete();
});
return response()->json(['success' => true, 'message' => 'Jenis kendaraan dan tarif terkait berhasil dihapus.']);
}
}
@@ -49,12 +49,13 @@ public function store(Request $request)
*/
public function show($id)
{
// Log::info($id);
$id = str($id);
$vehicleRates = DB::table("tarif")->where("id_mobil", $id)->first();
$vehicleRates = DB::table("tarif")->where("id_mobil", (string) $id)->first();
Log::info(json_encode($vehicleRates));
return $vehicleRates;
if (!$vehicleRates) {
return response()->json(['status' => false, 'message' => 'Tarif kendaraan tidak ditemukan.'], 404);
}
return response()->json($vehicleRates);
}
/**
@@ -77,14 +78,43 @@ public function edit($id)
*/
public function update(Request $request, $id)
{
Log::info($request->all());
// $id = $request->id_mobil;
$dataQuery = MasterTarif::find($id);
try {
Log::info($request->all());
if (!$dataQuery) {
return response()->json(['status' => false, 'message' => 'Tarif kendaraan tidak ditemukan.'], 404);
}
$dataQuery->fill($request->all());
$validated = $request->validate([
'waktu_gratis' => 'nullable|integer|min:0',
'tarif_period' => 'nullable|numeric|min:0',
'frek_interval' => 'nullable|integer|min:1',
'interval' => 'nullable|integer|min:1',
'tarif_interval' => 'nullable|numeric|min:0',
'bayar_dimuka' => 'nullable|boolean',
'asuransi_incl' => 'nullable|numeric|min:0',
'frek_waktu2' => 'nullable|integer|min:1',
'waktu2' => 'nullable|integer|min:1',
'tarif2' => 'nullable|numeric|min:0',
'tarif' => 'required|numeric|min:0',
'waktu_tarif' => 'nullable|integer|min:1',
'maksimum' => 'nullable|numeric|min:0',
'maksimum_mode' => 'required|in:hour,day',
'maksimum_hour' => 'nullable|integer|min:1',
'maksimum_day_minute' => 'nullable|integer|min:0',
'denda' => 'nullable|numeric|min:0',
'denda_member' => 'nullable|integer|min:0',
'denda_only_casual' => 'nullable|boolean',
'denda_only' => 'nullable|boolean',
]);
try {
$data = collect($validated)->except(['maksimum_mode', 'maksimum_hour', 'maksimum_day_minute'])->all();
$data['time_base'] = $validated['maksimum_mode'] === 'hour' ? 1 : 0;
$data['time_base_maks'] = $validated['maksimum_mode'] === 'hour' ? $validated['maksimum_hour'] : null;
$data['min_per_hari'] = $validated['maksimum_mode'] === 'day' ? $validated['maksimum_day_minute'] : null;
$data['denda_only_casual'] = $request->boolean('denda_only_casual') ? 1 : 0;
$data['denda_only'] = $request->boolean('denda_only') ? 1 : 0;
$dataQuery->fill($data);
if (!$dataQuery->isDirty()) {
return response()->json([
@@ -92,7 +122,6 @@ public function update(Request $request, $id)
"message" => "Tidak ada perubahan data",
]);
}
Log::info("Data yang di simpan" . json_encode($dataQuery->getDirty()));
$dataQuery->save();
return response()->json([
@@ -100,10 +129,7 @@ public function update(Request $request, $id)
"message" => "Data berhasil di ubah",
]);
} catch (\Throwable $th) {
Log::info("Data yang di simpan" . json_encode($dataQuery->getDirty()));
// Log::info($request->all());
Log::error('Gagal memperbarui tarif kendaraan', ['id_mobil' => $id, 'error' => $th->getMessage()]);
return response()->json([
"status" => false,
"message" => "Gagal menyimpan data " . $th->getMessage(),
@@ -11,18 +11,31 @@ class SetupController extends Controller
{
public function index()
{
$status = [
'pgcrypto' => $this->checkPgcrypto(),
'pegawai_columns' => $this->checkPegawaiColumns(),
'users_view' => $this->checkUsersView(),
'veri_time' => Schema::hasColumn('transaksi_parkir', 'veri_time'),
];
$status = $this->status();
return view('welcome', compact('status'));
return view('welcome', ['isReady' => $this->isReady($status)]);
}
public function setup()
{
$status = $this->status();
if ($this->isReady($status)) {
return redirect()->route('login');
}
return view('setup', compact('status'));
}
public function run()
{
if ($this->isReady($this->status())) {
return response()->json([
'success' => false,
'message' => 'Setup database sudah selesai dan tidak perlu dijalankan ulang.',
], 403);
}
DB::beginTransaction();
try {
@@ -66,6 +79,18 @@ public function run()
});
}
// 5. Laravel database sessions for authenticated web requests.
if (!Schema::hasTable('sessions')) {
Schema::create('sessions', function ($table) {
$table->string('id')->primary();
$table->unsignedBigInteger('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
DB::commit();
// return redirect()->back()->with('success', 'Setup berhasil dijalankan ✅');
@@ -84,6 +109,26 @@ public function run()
}
}
private function status(): array
{
return [
'pgcrypto' => $this->checkPgcrypto(),
'pegawai_columns' => $this->checkPegawaiColumns(),
'users_view' => $this->checkUsersView(),
'veri_time' => Schema::hasColumn('transaksi_parkir', 'veri_time'),
'sessions' => Schema::hasTable('sessions'),
];
}
private function isReady(array $status): bool
{
return $status['pgcrypto']
&& $status['users_view']
&& $status['veri_time']
&& $status['sessions']
&& !in_array(false, $status['pegawai_columns'], true);
}
/* =========================
Helper
========================= */
@@ -0,0 +1,165 @@
<?php
namespace App\Http\Controllers\ParkingLot;
use App\Http\Controllers\Controller;
use App\Models\LotParking\SiteParking;
use App\Models\LotParking\LotParkingLog;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
class ParkingLotComponent extends Controller
{
/**
* Halaman utama parking lot
*/
public function index()
{
$getArea = SiteParking::all(); // Mengambil semua data dari model SiteParking
$parkingDataByArea = []; // Array untuk menyimpan hasil query berdasarkan area
foreach ($getArea as $lantai) {
$spaceParking = $lantai->space_parking;
// Array untuk menyimpan data parking per lantai
$parkingData = [];
for ($lotNumber = 1; $lotNumber <= $spaceParking; $lotNumber++) {
// Melakukan query pada LotParkingLog berdasarkan area dan lot_number
$log = LotParkingLog::where("area", $lantai->sitearea_id)
->where("lot_number", $lotNumber)
->select("lot_number", "status")
->first();
// Jika data tidak ditemukan, buat object baru dengan lot_number dari pengulangan for
if (!$log) {
$log = (object) [
"lot_number" => $lotNumber,
"status" => 0,
];
}
// Menyimpan hasil query dalam array
$parkingData[] = $log;
}
// Menyimpan data parking per lantai dalam array berdasarkan area
$parkingDataByArea[] = [
"lantai" => $lantai->sitearea_id,
"data" => $parkingData,
];
}
// Melakukan dump and die untuk melihat hasil query sementara
// dd(json_encode($parkingDataByArea));
// dd($getArea->toJson());
return view("livewire.space", [
"areas" => $getArea,
"parkingDataByArea" => $parkingDataByArea,
]);
// return view("liveware.space");
}
public function getParkingData()
{
Log::info("getParkingData method called");
$getArea = SiteParking::get();
Log::info("Mendapatkan area: " . json_encode($getArea->toArray()));
$parkingDataByArea = [];
foreach ($getArea as $lantai) {
$areaName = $lantai->name;
$spaceParking = (int) $lantai->space_parking;
Log::info("Mencari lot di lantai: {$areaName}, jumlah lot: {$spaceParking}");
$parkingData = [];
for ($lotNumber = 1; $lotNumber <= $spaceParking; $lotNumber++) {
$log = LotParkingLog::where("area", $areaName)
->where("lot_number", $lotNumber)
->where("status", 1)
->select("lot_number", "status")
->orderByDesc("updated_at")
->first();
Log::info("Lot {$areaName} #{$lotNumber}: " . json_encode($log));
if (!$log) {
$log = (object) [
"lot_number" => $lotNumber,
"status" => 0,
];
}
$parkingData[] = $log;
}
$parkingDataByArea[] = [
"lantai" => $areaName,
"data" => $parkingData,
];
}
return response()->json($parkingDataByArea);
}
// public function index()
// {
// $parkingDataByArea = $this->getParkingData();
// return view("livewire.index", compact("parkingDataByArea"));
// }
/**
* Endpoint AJAX untuk refresh data parking
*/
public function data()
{
$parkingDataByArea = $this->getParkingData();
return view("livewire.partial-lot", compact("parkingDataByArea"));
}
/**
* Ambil data parking
*/
// private function getParkingData()
// {
// $getArea = SiteParking::all();
// $parkingDataByArea = [];
// foreach ($getArea as $lantai) {
// $spaceParking = $lantai->space_parking;
// $parkingData = [];
// for ($lotNumber = 1; $lotNumber <= $spaceParking; $lotNumber++) {
// $log = LotParkingLog::where("area", $lantai->sitearea_id)
// ->where("lot_number", $lotNumber)
// ->select("lot_number", "status")
// ->orderByDesc("created_at")
// ->first();
// if (!$log) {
// $log = (object) [
// "lot_number" => $lotNumber,
// "status" => 0,
// ];
// }
// $parkingData[] = $log;
// }
// $parkingDataByArea[] = [
// "lantai" => $lantai->sitearea_id,
// "data" => $parkingData,
// ];
// }
// return $parkingDataByArea;
// }
}
@@ -1,84 +0,0 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ParkingSetupUserLevelLevelMenuController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}
+1
View File
@@ -32,6 +32,7 @@ class Kernel extends HttpKernel
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\App\Http\Middleware\UseBootstrapSessionStore::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
@@ -0,0 +1,20 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Schema;
class UseBootstrapSessionStore
{
public function handle(Request $request, Closure $next)
{
if (config('session.driver') === 'database' && !Schema::hasTable(config('session.table'))) {
// A legacy database may not have Laravel's session table yet.
config(['session.driver' => 'file']);
}
return $next($request);
}
}
+8
View File
@@ -11,6 +11,14 @@ class ConfigHardware extends Model
protected $table = 'config_pos_hardware';
// # tabel ini tidak punya kolom id, kuncinya id_pos (satu baris per pintu)
protected $primaryKey = 'id_pos';
public $incrementing = false;
protected $keyType = 'string';
// # tabel ini tidak punya kolom created_at / updated_at
public $timestamps = false;
protected $fillable = [
'printer1_avail','printer1_conn','printer1_spd','printer1_stdfile','printer1_autocutter','printer1_bgconnect',
'printer1_dummy','video1_avail','video1_conn','video1_channel','video1_version','feedisplay_avail',
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ConfigPosNetwork extends Model
{
use HasFactory;
protected $primaryKey = "id_pos";
public $incrementing = false;
protected $keyType = "int";
protected $table = "config_pos_network";
protected $fillable = ["id_pos", "hostname", "mac_address", "ip_address", "enabled"];
protected $casts = [
"enabled" => "boolean",
];
}
+24
View File
@@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class IntercomCall extends Model
{
use HasFactory;
protected $table = 'intercom_calls';
protected $fillable = [
'device_id', 'arah', 'status',
'requested_at', 'started_at', 'ended_at',
'durasi_detik', 'recording_path', 'ended_by',
];
protected $casts = [
'requested_at' => 'datetime',
'started_at' => 'datetime',
'ended_at' => 'datetime',
];
}
+21
View File
@@ -0,0 +1,21 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class IntercomDevice extends Model
{
use HasFactory;
protected $table = 'intercom_devices';
protected $fillable = [
'device_id', 'nama', 'mac_address', 'ip_address',
'udp_port', 'status', 'last_seen_at',
];
protected $casts = [
'last_seen_at' => 'datetime',
];
}
+3 -6
View File
@@ -9,14 +9,11 @@ class JenisMobil extends Model
{
use HasFactory;
protected $table = 'jenis_mobil';
protected $table = "jenis_mobil";
protected $casts = [
'id' => 'string',
];
protected $fillable = [
'nama','short_cut'
"id" => "string",
];
protected $fillable = ["id", "nama", "short_cut", "validasi"];
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\Models\LotParking;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class LotParkingLog extends Model
{
use HasFactory;
protected $table = "lot_parking_log";
protected $fillable = ["area", "lot_number", "status", "created_at", "updated_at"];
// public function siteUser()
// {
// return $this->belongsTo(User::class,'id','id');
// }
// public function siteGroups()
// {
// return $this->belongsTo(User::class, 'site_group', 'company');
// }
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\Models\LotParking;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class SiteParking extends Model
{
use HasFactory;
protected $table = "site_parkinglot";
protected $fillable = ["sitearea_id", "name", "space_parking"];
// public function siteUser()
// {
// return $this->belongsTo(User::class,'id','id');
// }
// public function siteGroups()
// {
// return $this->belongsTo(User::class, 'site_group', 'company');
// }
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class MasterTarifStiker extends Model
{
use HasFactory;
protected $table = "tarif_stiker";
// protected $primaryKey = "id_mobil"; // Primary key tabel
public $timestamps = false;
protected $fillable = [
"id_mobil','jenis_langganan','tarif','tgl_akhir','last_member','jenis','jenis_member',
'hitung_awal_bulan','tarif_id','available_online','langganan','kode_akses','stasiunkerja_id",
];
public function tarifStiker()
{
return $this->hasOne(StikerTransDetail::class, "jenis_member", "jenis_langganan");
}
}
@@ -0,0 +1,65 @@
<?php
namespace App\Models\Origin;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class MasterJenisLangganan extends Model
{
/**
* Nama tabel di database.
*
* @var string
*/
protected $table = "jenis_langganan";
/**
* Kolom "id" di tabel ini varchar dan bukan auto-increment,
* tapi tetap dipakai sebagai identifier utama secara logis.
*
* @var string
*/
protected $primaryKey = "id";
/**
* Karena id bukan integer auto-increment.
*
* @var bool
*/
public $incrementing = false;
/**
* Tipe data primary key.
*
* @var string
*/
protected $keyType = "string";
/**
* Tabel ini tidak memiliki kolom created_at / updated_at.
*
* @var bool
*/
public $timestamps = false;
/**
* Kolom yang boleh diisi secara mass assignment.
*
* @var array<int, string>
*/
protected $fillable = ["id", "nama", "bayar", "tarif", "periode_awal", "masa_berlaku", "maks", "jenis_mobil"];
/**
* Casting tipe data kolom.
*
* @var array<string, string>
*/
protected $casts = [
"bayar" => "integer",
"tarif" => "integer",
"periode_awal" => "datetime",
"masa_berlaku" => "integer",
"maks" => "integer",
];
}
+88
View File
@@ -0,0 +1,88 @@
<?php
namespace App\Models\Origin;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany; // <-- tambahin ini
class MasterStasiunKerja extends Model
{
/**
* Nama tabel di database.
*
* @var string
*/
protected $table = "stasiunkerja";
/**
* "kode" dipakai sebagai identifier utama secara logis
* (tidak ada PK asli di tabel ini).
*
* @var string
*/
protected $primaryKey = "kode";
/**
* kode bukan integer auto-increment.
*
* @var bool
*/
public $incrementing = false;
/**
* Tipe data primary key.
*
* @var string
*/
protected $keyType = "string";
/**
* Tabel ini tidak memiliki kolom created_at / updated_at.
*
* @var bool
*/
public $timestamps = false;
/**
* Kolom yang boleh diisi secara mass assignment.
*
* @var array<int, string>
*/
protected $fillable = [
"kode",
"nama",
"margin",
"spv",
"target",
"id_lokasi",
"lantai",
"status",
"alamat",
"bank",
"telp",
"kode_akses",
];
/**
* Casting tipe data kolom.
*
* @var array<string, string>
*/
protected $casts = [
"margin" => "integer",
"target" => "integer",
"status" => "integer",
];
/**
* Relasi ke tarif_stiker.
* stasiunkerja.kode = tarif_stiker.langganan
*
* @return HasMany
*/
public function tarifStiker(): HasMany
{
return $this->hasMany(MasterTarifStiker::class, "langganan", "kode");
}
}
+90
View File
@@ -0,0 +1,90 @@
<?php
namespace App\Models\Origin;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; // <-- tambahin ini
class MasterTarifStiker extends Model
{
/**
* Nama tabel di database.
*
* @var string
*/
protected $table = "tarif_stiker";
/**
* "tarif_id" dipakai sebagai identifier utama secara logis
* (kolom bigint not null, tidak ada PK asli di tabel ini).
* Karena belum pasti ini sequence auto-increment di DB,
* incrementing sengaja dimatikan. Ubah ke true kalau
* ternyata kolom ini memang identity/serial di database.
*
* @var string
*/
protected $primaryKey = "tarif_id";
/**
* @var bool
*/
public $incrementing = false;
/**
* Tipe data primary key.
*
* @var string
*/
protected $keyType = "integer";
/**
* Tabel ini tidak memiliki kolom created_at / updated_at.
*
* @var bool
*/
public $timestamps = false;
/**
* Kolom yang boleh diisi secara mass assignment.
*
* @var array<int, string>
*/
protected $fillable = [
"id_mobil",
"jenis_langganan",
"tarif",
"last_member",
"tgl_akhir",
"jenis",
"jenis_member",
"hitung_awal_bulan",
"tarif_id",
"available_online",
"langganan",
"kode_akses",
];
/**
* Casting tipe data kolom.
*
* @var array<string, string>
*/
protected $casts = [
"tarif" => "float",
"hitung_awal_bulan" => "integer",
"tarif_id" => "integer",
"available_online" => "boolean",
];
/**
* Relasi ke stasiunkerja.
* tarif_stiker.langganan = stasiunkerja.kode
*
* @return BelongsTo
*/
public function stasiunKerja(): BelongsTo
{
return $this->belongsTo(MasterStasiunKerja::class, "langganan", "kode");
}
}
+36 -8
View File
@@ -2,6 +2,8 @@
namespace App\Models;
use App\Models\StikerTransDetail;
use App\Models\MemberCard;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@@ -9,24 +11,50 @@ class StikerTrans extends Model
{
use HasFactory;
protected $table = 'transaksi_stiker';
protected $table = "transaksi_stiker";
protected $fillable = [
'notrans','nama','alamat', 'telepon', 'jenis_transaksi', 'awal', 'harga', 'keterangan', 'tanggal' , 'operator' ,
'akhir' , 'maks' , 'no_id' , 'unit_kerja', 'no_induk' , 'jenis_stiker' , 'hari_ke' ,'jenis_langganan' ,
'exit_pass' , 'no_kuitansi', 'tgl_edited', 'tipe_exit_pass', 'seq_code' , 'unitno' , 'area' , 'reserved',
'batasan_jam', 'batasan_awal', 'batasan_akhir', 'asuransi'
"notrans",
"nama",
"alamat",
"telepon",
"jenis_transaksi",
"awal",
"harga",
"keterangan",
"tanggal",
"operator",
"akhir",
"maks",
"no_id",
"unit_kerja",
"no_induk",
"jenis_stiker",
"hari_ke",
"jenis_langganan",
"exit_pass",
"no_kuitansi",
"tgl_edited",
"tipe_exit_pass",
"seq_code",
"unitno",
"area",
"reserved",
"batasan_jam",
"batasan_awal",
"batasan_akhir",
"asuransi",
];
public $with = ['stiker_details', 'memberCard'];
// public $with = ["stiker_details", "memberCard"];
public function stiker_details()
{
return $this->hasMany(StikerTransDetail::class, 'notrans', 'notrans');
return $this->hasMany(StikerTransDetail::class, "notrans", "notrans");
}
public function memberCard()
{
return $this->hasMany(MemberCard::class, 'notrans', 'no_id');
return $this->hasMany(MemberCard::class, "notrans", "no_id");
}
}
+30 -6
View File
@@ -2,6 +2,9 @@
namespace App\Models;
use App\Models\StikerTrans;
use App\Models\VehicleItem;
use App\Models\MasterTarifStiker;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
@@ -10,22 +13,43 @@ class StikerTransDetail extends Model
{
use HasFactory;
protected $table = 'detail_transaksi_stiker';
protected $table = "detail_transaksi_stiker";
protected $fillable = [
'notrans', 'nopol', 'jenis_mobil', 'adm', 'kategori',
'jenis_member', 'akses', 'akses_out', 'status', 'merk',
'tipe', 'tahun', 'warna', 'keterangan',
"notrans",
"nopol",
"jenis_mobil",
"adm",
"kategori",
"jenis_member",
"akses",
"akses_out",
"status",
"merk",
"tipe",
"tahun",
"warna",
"keterangan",
];
public function stiker_details()
{
return $this->belongsTo(StikerTrans::class, 'notrans', 'notrans');
return $this->belongsTo(StikerTrans::class, "notrans", "notrans");
}
public function tarifStiker()
{
return $this->hasOne(MasterTarifStiker::class, "jenis_member", "jenis_langganan");
}
// Relasi ke tabel jenis_mobil
public function vehicle()
{
return $this->belongsTo(VehicleItem::class, 'id', 'jenis_mobil');
return $this->belongsTo(VehicleItem::class, "jenis_mobil", "id");
}
public function details()
{
return $this->with(["vehicle:id, nama", "tarifStiker:jenis_langganan,tarif"]);
}
}
+55 -67
View File
@@ -7,116 +7,104 @@
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Auth;
class MakeStikerServices
{
public function stikerCreate($request, Carbon $awal, Carbon $akhir)
{
Log::info(json_encode($sticker));
return DB::transaction(function () use ($sticker) {
Log::info("Data yang akan di proses : " . json_encode($request));
return DB::transaction(function () use ($request) {
// LOCK index agar aman
$index = DB::table("indexnum")->where("nama", "sticker")->lockForUpdate()->first();
$mountYearNow = Carbon::now()->format("m/Y");
// $index = DB::table("indexnum")->where("nama", "sticker")->lockForUpdate()->first();
// $mountYearNow = Carbon::now()->format("m-Y");
// $nextIndex = str_pad($index->value + 1, 4, "0", STR_PAD_LEFT);
$nextIndex = str_pad($index->value + 1, 4, "0", STR_PAD_LEFT);
// Ambil sequence Stiker dan Identitas
// $seqTrans = DB::table("indexnum")->select("value")->where("nama", "sticker")->first();
// $seqId = DB::table("indexnum")->select("value")->where("nama", "identitas")->first();
$seqTrans = DB::table("indexnum")->select("value")->where("nama", "sticker")->first();
$seqId = DB::table("indexnum")->select("value")->where("nama", "identitas")->first();
$notrans = $seqTrans->value . "/STK/" . $mountYearNow;
$noid = $seqId->value . "/IDT/" . $mountYearNow;
// Parsing format stiker dan identitas
// $notrans = $seqTrans->value . "-STK-" . $mountYearNow;
// $noid = $seqId->value . "-IDT-" . $mountYearNow;
$transStiker = TransaksiStiker::where("notrans", $sticker->notrans)->first();
$detailTransStiker = DetailTransaksiStiker::where("notrans", $sticker->notrans)->first();
Log::info(json_encode($transStiker));
Log::info(json_encode($detailTransStiker));
// HEADER
// $periode = explode(" - ", $dataStiker["periode"]);
// $awal = Carbon::createFromFormat("d-m-Y H:i", $periode[0]);
// $akhir = Carbon::createFromFormat("d-m-Y H:i", $periode[1]);
$user = Auth::user();
$dateNow = Carbon::now()->format("Y-m-d H:i:s");
// Parcing data yang diterima
$dataStiker = $request["dataSticker"];
$dataVehicle = $request["dataVehicle"];
$dataCard = $request["dataCard"];
try {
$dataStiker = [
"jenis_transaksi" => $dataStiker["jenis_transaksi"], // 0 = transaksi baru , 1 = perpanjangan
"notrans" => $notrans,
"no_id" => $noid,
"notrans" => $dataStiker["notrans"],
"no_id" => $dataStiker["no_id"],
"nama" => $dataStiker["nama"],
"alamat" => $dataStiker["address"] ?? "",
"telepon" => $dataStiker["phone"] ?? "",
"unit_kerja" => $dataStiker["depart"],
"jenis_stiker" => $dataStiker["member"],
"awal" => $awal,
"akhir" => $akhir,
"awal" => $dataStiker["awal"],
"akhir" => $dataStiker["akhir"],
"harga" => $dataStiker["harga"] ?? 0,
"keterangan" => $dataStiker["note"] ?? "",
"tanggal" => $dateNow,
"operator" => $user->nomer,
"operator" => $user?->nomer ?? "",
];
DB::table("transaksi_stiker")->insert($dataStiker);
// DETAIL
DB::table("detail_transaksi_stiker")->insert([
"notrans" => $notrans,
"nopol" => $detailTransStiker->nopol,
"jenis_mobil" => $detailTransStiker->jenis_mobil,
"adm" => $detailTransStiker->adm,
"kategori" => $detailTransStiker->kategori,
"jenis_member" => $detailTransStiker->jenis_member,
"akses" => $detailTransStiker->akses,
"akses_out" => $detailTransStiker->akses_out,
"status" => $detailTransStiker->status,
"merk" => $detailTransStiker->merk,
"tipe" => $detailTransStiker->tipe,
"tahun" => $detailTransStiker->tahun,
"warna" => $detailTransStiker->warna,
"keterangan" => $detailTransStiker->keterangan,
]);
DB::table("transaksi_stiker")->insert($dataStiker);
if ($dataVehicle) {
if (!empty($dataVehicle)) {
foreach ($dataVehicle as $nopol) {
$dataVehicle = [
"notrans" => $notrans,
Log::info(json_encode($nopol));
$produkMember = explode(" - ", $nopol["member"]);
$insertVehicle = [
"notrans" => $nopol["notrans"],
"nopol" => $nopol["nopol"],
"jenis_mobil" => $nopol["vehicle"],
"jenis_member" => $nopol["member"],
"jenis_member" => $produkMember[0],
"merk" => $nopol["merk"],
];
DB::table("detail_transaksi_stiker")->insert($dataVehicle);
DB::table("detail_transaksi_stiker")->insert($insertVehicle);
}
} else {
return 0;
}
if ($dataCard) {
if (!empty($dataCard)) {
foreach ($dataCard as $cardNumber) {
$dataCard = [
"notrans" => $noid,
$insertCard = [
"notrans" => $cardNumber["no_id"],
"no_card" => $cardNumber["nocard"],
"no_urut" => $cardNumber["nourut"],
];
DB::table("card_member")->insert($dataCard);
DB::table("card_member")->insert($insertCard);
}
} else {
return 0;
}
DB::table("indexnum")
->where("nama", "sticker")
->update(["value" => DB::raw("lpad(to_number(value,'9999')+1)::text, 4, '0')")]);
DB::table("indexnum")
->where("nama", "identitas")
->update(["value" => DB::raw("lpad(to_number(value,'9999')+1)::text, 4, '0')")]);
$response = true;
$msg = "Data berhasil di simpan";
// DB::table("indexnum")
// ->where("nama", "sticker")
// // ->update(["value" => DB::raw("lpad(to_number(value,'9999')+1)::text, 4, '0')")]);
// ->update(["value" => $newNoTrans]);
// DB::table("indexnum")
// ->where("nama", "identitas")
// // ->update(["value" => DB::raw("lpad(to_number(value,'9999')+1)::text, 4, '0')")]);
// ->update(["value" => $newNoID]);
return response()->json([
"status" => true,
"message" => "Data berhasil di simpan",
]);
} catch (Exception $e) {
$response = false;
$msg = "Gagal menyimpan data " . $e->getMessage() . $e->getLine();
return response()->json([
"status" => false,
"message" => "Gagal menyimpan data " . $e->getMessage() . $e->getLine(),
]);
}
DB::table("indexnum")
->where("nama", "sticker")
->update(["value" => $nextIndex]);
return $notrans;
});
}
+195
View File
@@ -0,0 +1,195 @@
<?php
namespace App\Services\Parking;
use App\Models\ConfigPosNetwork;
use Illuminate\Support\Facades\Log;
use Symfony\Component\Process\Process;
use RuntimeException;
class NetworkService
{
private string $tempFile = "/var/lib/dhcp-panel/reservations.conf";
private string $lastAppliedFile = "/var/lib/dhcp-panel/last-applied";
public function apply(): void
{
Log::info("DHCP APPLY DEBUG", [
"uid" => function_exists("posix_geteuid") ? posix_geteuid() : null,
"user" => get_current_user(),
"temp_file" => $this->tempFile,
"file_exists" => file_exists($this->tempFile),
"dir_writable" => is_writable(dirname($this->tempFile)),
"file_writable" => file_exists($this->tempFile) ? is_writable($this->tempFile) : null,
]);
$config = $this->generate();
if (file_put_contents($this->tempFile, $config) === false) {
throw new RuntimeException("Failed to write temporary DHCP configuration.");
}
$process = new Process(["sudo", "/usr/local/bin/dhcp-reservation", "apply"]);
$process->run();
if (!$process->isSuccessful()) {
throw new RuntimeException(trim($process->getErrorOutput() ?: $process->getOutput()));
}
// if (!$process->isSuccessful()) {
// throw new RuntimeException(...);
// }
// HANYA disimpan kalau apply benar-benar berhasil
$this->saveLastApplied();
}
public function status(): array
{
$service = "isc-dhcp-server";
$running = false;
$process = new \Symfony\Component\Process\Process(["systemctl", "is-active", $service]);
$process->run();
if ($process->isSuccessful()) {
$running = trim($process->getOutput()) === "active";
}
return [
"running" => $running,
"hostname" => $this->hostname(),
"ip" => $this->serverIp(),
"interface" => config("network.dhcp_interface", "eth0"),
"status" => $running ? "running" : "stopped",
"active_clients" => $this->activeClients(),
"last_applied" => $this->lastApplied(),
];
}
public function lastApplied(): ?string
{
if (!file_exists($this->lastAppliedFile)) {
return null;
}
return trim(file_get_contents($this->lastAppliedFile));
}
public function logs(int $limit = 30): array
{
$process = new Process([
"journalctl",
"-u",
$this->dhcpServiceName(),
"-n",
(string) $limit,
"--no-pager",
"-o",
"short",
]);
$process->setTimeout(5);
$process->run();
if (!$process->isSuccessful()) {
return [];
}
return collect(preg_split('/\r\n|\r|\n/', trim($process->getOutput())))->filter()->values()->all();
}
private function dhcpServiceName(): string
{
return config("network.dhcp_service", "isc-dhcp-server");
}
private function hostname(): string
{
return trim(gethostname() ?: php_uname("n"));
}
private function serverIp(): ?string
{
$interface = config("network.dhcp_interface", "enx00e04c6802a9");
$process = new Process(["ip", "-4", "-o", "addr", "show", "dev", $interface]);
$process->run();
if (!$process->isSuccessful()) {
return null;
}
if (preg_match("/inet\s+([0-9.]+)\//", $process->getOutput(), $matches)) {
return $matches[1];
}
return null;
}
private function generate(): string
{
$reservations = ConfigPosNetwork::query()->where("enabled", true)->orderBy("hostname")->get();
$config = "";
foreach ($reservations as $reservation) {
$config .= <<<CONF
host {$reservation->hostname} {
hardware ethernet {$reservation->mac_address};
fixed-address {$reservation->ip_address};
}
CONF;
}
return ltrim($config);
}
private function activeClients(): int
{
$leaseFile = "/var/lib/dhcp/dhcpd.leases";
if (!file_exists($leaseFile)) {
return 0;
}
$content = file_get_contents($leaseFile);
preg_match_all("/lease\s+([0-9.]+)\s*\{(.*?)\}/s", $content, $matches, PREG_SET_ORDER);
$leases = [];
foreach ($matches as $match) {
$ip = $match[1];
$block = $match[2];
preg_match("/binding state\s+(\w+);/", $block, $state);
$leases[$ip] = [
"state" => $state[1] ?? null,
];
}
return collect($leases)->where("state", "active")->count();
}
private function saveLastApplied(): void
{
$directory = dirname($this->lastAppliedFile);
if (!is_dir($directory)) {
mkdir($directory, 0755, true);
}
file_put_contents($this->lastAppliedFile, now()->toDateTimeString());
}
}
@@ -0,0 +1,43 @@
<?php
namespace App\Services\Parking;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class ParkingUtilsServices
{
private function isMirror($user)
{
return $user->statusabsen == 1;
}
private function pgConnectSecond($site_config = null)
{
try {
Config::set("database.connections.pgsql_second", [
"driver" => "pgsql",
"host" => $site_config->site_host,
"user" => $site_config->site_user,
"password" => $site_config->site_password,
"database" => $site_config->site_database,
"port" => $site_config->site_port,
"charset" => "utf8",
"prefix" => "",
"schema" => "public",
]);
return DB::table("pgsql_second");
} catch (\Exception $e) {
Log::error("Failed connect to database " . $e->getMessage());
return null;
}
}
public function useTableParkir($user)
{
$table = $this->isMirror($user) ? "parkir_transaksi" : "transaksi_parkir";
return DB::table("$table as tp");
}
}
+7 -3
View File
@@ -19,16 +19,20 @@ public function parkingTimeCalc($vehicle, $times)
Log::info("Mulai perhitungan tarif dengan durasi : " . $duration . " (Rp. " . $parkingPrice . ")");
// Grace Periode
// if ($duration < $tarifVehicle->waktu_gratis) {
// waktu_gratis kesimpen dalam MENIT (sama kayak interval/waktu2/waktu_tarif
// di bawah), tapi $duration dalam DETIK -- makanya WAJIB dikali 60 dulu biar
// satuannya sama, sama kayak pola interval*60/waktu2*60/waktu_tarif*60 yang
// dipake di bagian lain fungsi ini.
// if ($duration < $tarifVehicle->waktu_gratis * 60) {
// Log::info("GP Rules : ");
// return $parkingPrice;
// } elseif (
// $tarifVehicle->waktu_gratis !== 0 &&
// $tarifVehicle->tarif_periode !== 0
// $tarifVehicle->tarif_period !== 0
// ) {
// Log::info("GP Rules : ");
// $parkingPrice = $parkingPrice + $tarifVehicle->tarif_period;
// $duration = $duration - $tarifVehicle->waktu_gratis;
// $duration = $duration - ($tarifVehicle->waktu_gratis * 60);
// Log::info(
// "Setelah GP : " .
// $parkingPrice .
+2 -1
View File
@@ -11,7 +11,8 @@
"laravel/framework": "^9.0",
"laravel/sanctum": "^2.14",
"laravel/tinker": "^2.7",
"laravel/ui": "^4.6"
"laravel/ui": "^4.6",
"yajra/laravel-datatables-oracle": "^10.11"
},
"require-dev": {
"fakerphp/faker": "^1.9.1",
Generated
+91 -4
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "006c4ddfd87627edd18542c501fd181e",
"content-hash": "073567f86abe3c9b30d540058b70a726",
"packages": [
{
"name": "asm89/stack-cors",
@@ -5793,6 +5793,93 @@
"source": "https://github.com/webmozarts/assert/tree/1.11.0"
},
"time": "2022-06-03T18:03:27+00:00"
},
{
"name": "yajra/laravel-datatables-oracle",
"version": "v10.11.4",
"source": {
"type": "git",
"url": "https://github.com/yajra/laravel-datatables.git",
"reference": "a11dc9cf5ec7f7dc68ad0d5959c5e7aec0af29a5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yajra/laravel-datatables/zipball/a11dc9cf5ec7f7dc68ad0d5959c5e7aec0af29a5",
"reference": "a11dc9cf5ec7f7dc68ad0d5959c5e7aec0af29a5",
"shasum": ""
},
"require": {
"illuminate/database": "^9|^10",
"illuminate/filesystem": "^9|^10",
"illuminate/http": "^9|^10",
"illuminate/support": "^9|^10",
"illuminate/view": "^9|^10",
"php": "^8.0.2"
},
"require-dev": {
"algolia/algoliasearch-client-php": "^3.4",
"larastan/larastan": "^2.4",
"laravel/scout": "^10.5",
"meilisearch/meilisearch-php": "^1.4",
"orchestra/testbench": "^8",
"yajra/laravel-datatables-html": "^9.3.4|^10"
},
"suggest": {
"yajra/laravel-datatables-buttons": "Plugin for server-side exporting of dataTables.",
"yajra/laravel-datatables-editor": "Plugin to use DataTables Editor (requires a license).",
"yajra/laravel-datatables-export": "Plugin for server-side exporting using livewire and queue worker.",
"yajra/laravel-datatables-fractal": "Plugin for server-side response using Fractal.",
"yajra/laravel-datatables-html": "Plugin for server-side HTML builder of dataTables."
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"DataTables": "Yajra\\DataTables\\Facades\\DataTables"
},
"providers": [
"Yajra\\DataTables\\DataTablesServiceProvider"
]
},
"branch-alias": {
"dev-master": "10.x-dev"
}
},
"autoload": {
"files": [
"src/helper.php"
],
"psr-4": {
"Yajra\\DataTables\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Arjay Angeles",
"email": "[email protected]"
}
],
"description": "jQuery DataTables API for Laravel 4|5|6|7|8|9|10",
"keywords": [
"datatables",
"jquery",
"laravel"
],
"support": {
"issues": "https://github.com/yajra/laravel-datatables/issues",
"source": "https://github.com/yajra/laravel-datatables/tree/v10.11.4"
},
"funding": [
{
"url": "https://github.com/sponsors/yajra",
"type": "github"
}
],
"time": "2024-02-28T05:00:23+00:00"
}
],
"packages-dev": [
@@ -8326,12 +8413,12 @@
],
"aliases": [],
"minimum-stability": "dev",
"stability-flags": [],
"stability-flags": {},
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
"php": "^8.0"
},
"platform-dev": [],
"plugin-api-version": "2.6.0"
"platform-dev": {},
"plugin-api-version": "2.9.0"
}
+7
View File
@@ -0,0 +1,7 @@
<?php
return [
"dhcp_interface" => env("DHCP_INTERFACE", "wlp1s0"),
"dhcp_service" => env("DHCP_SERVICE", "isc-dhcp-server"),
];
@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create("config_pos_network", function (Blueprint $table) {
$table->string("id_pos");
$table->string("hostname", 100);
$table->string("mac_address", 17)->unique();
$table->string("ip_address")->unique();
$table->boolean("enabled")->default(true);
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists("dhcp_reservations");
}
};
@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create("lot_parking_log", function (Blueprint $table) {
$table->uuid();
$table->string("area", 8);
$table->string("lot_number", 8);
$table->smallInteger("status");
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists("lot_parking_log");
}
};
@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('sessions');
}
};
@@ -0,0 +1,46 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up()
{
// Daftar unit: 1-5 client + 1 admin/pusat. Mirip master gate di parkir.
Schema::create('intercom_devices', function (Blueprint $table) {
$table->id();
$table->string('device_id', 20)->unique(); // "C1".."C5", "ADMIN"
$table->string('nama', 100)->nullable(); // "Pos Pintu 1", "Pusat"
$table->string('mac_address', 17)->nullable()->unique();
$table->string('ip_address', 45)->nullable();
$table->integer('udp_port')->nullable(); // 5001..5005, ADMIN 5000
$table->string('status', 20)->default('offline'); // offline|idle|calling|busy|active
$table->timestamp('last_seen_at')->nullable();
$table->timestamps();
});
// Log tiap sesi private Pusat<->Client. Mirip transaksi_parkir tapi versi suara.
Schema::create('intercom_calls', function (Blueprint $table) {
$table->id();
$table->string('device_id', 20)->index();
$table->string('arah', 20)->default('in'); // in: client->pusat, out: pusat->client
$table->string('status', 20)->default('waiting'); // waiting|active|ended|missed|cancelled
$table->timestamp('requested_at')->nullable();
$table->timestamp('started_at')->nullable();
$table->timestamp('ended_at')->nullable();
$table->integer('durasi_detik')->default(0);
$table->string('recording_path', 255)->nullable(); // storage/app/intercom/xxx.wav
$table->string('ended_by', 20)->nullable(); // client|admin|timeout
$table->timestamps();
$table->index(['device_id', 'status']);
});
}
public function down()
{
Schema::dropIfExists('intercom_calls');
Schema::dropIfExists('intercom_devices');
}
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

+98 -70
View File
@@ -1,74 +1,101 @@
$(document).ready(function () {
$(document).on('change', '.form-select', function () {
if ($('option:selected', this).val() == '') {
$(".nama").css('display', 'none');
$(".nopol").css('display', 'none');
$(".nocard").css('display', 'none');
$(".notrans").css('display', 'none');
$(document).on("change", ".form-select", function () {
if ($("option:selected", this).val() == "") {
$(".nama").css("display", "none");
$(".nopol").css("display", "none");
$(".nocard").css("display", "none");
$(".notrans").css("display", "none");
// $(".site").val('');
} else if ($('option:selected', this).val() == 'nama') {
$(".nama").css('display', 'block');
$(".nopol").css('display', 'none');
$(".nocard").css('display', 'none');
$(".notrans").css('display', 'none');
$(".input-default").val('');
} else if ($('option:selected', this).val() == 'nopol') {
$(".nama").css('display', 'none');
$(".nopol").css('display', 'block');
$(".nocard").css('display', 'none');
$(".notrans").css('display', 'none');
$(".input-default").val('');
} else if ($('option:selected', this).val() == 'nocard') {
$(".nama").css('display', 'none');
$(".nopol").css('display', 'none');
$(".nocard").css('display', 'block');
$(".notrans").css('display', 'none');
$(".input-default").val('');
} else if ($('option:selected', this).val() == 'notrans') {
$(".nama").css('display', 'none');
$(".nopol").css('display', 'none');
$(".nocard").css('display', 'none');
$(".notrans").css('display', 'block');
$(".input-default").val('');
} else if ($("option:selected", this).val() == "nama") {
$(".nama").css("display", "block");
$(".nopol").css("display", "none");
$(".nocard").css("display", "none");
$(".notrans").css("display", "none");
$(".input-default").val("");
} else if ($("option:selected", this).val() == "nopol") {
$(".nama").css("display", "none");
$(".nopol").css("display", "block");
$(".nocard").css("display", "none");
$(".notrans").css("display", "none");
$(".input-default").val("");
} else if ($("option:selected", this).val() == "nocard") {
$(".nama").css("display", "none");
$(".nopol").css("display", "none");
$(".nocard").css("display", "block");
$(".notrans").css("display", "none");
$(".input-default").val("");
} else if ($("option:selected", this).val() == "notrans") {
$(".nama").css("display", "none");
$(".nopol").css("display", "none");
$(".nocard").css("display", "none");
$(".notrans").css("display", "block");
$(".input-default").val("");
} else {
$(".position").css('display', 'none');
$(".level-area").css('display', 'none');
$(".input-default").val('');
$(".position").css("display", "none");
$(".level-area").css("display", "none");
$(".input-default").val("");
}
});
var url = $('#example').data('url');
var url = $("#example").data("url");
$('#search-button').on('click', function () {
// Ambil nilai dari selected option
var option = $('.select-option').val();
$("#search-button").on("click", function () {
let searchBy = $("#search-by").val();
let searchValue = $("#search-value").val().trim();
// Cek jika option yang dipilih bukan 'Pencarian Data...' yang berarti pilihan valid
if (option && option !== '') {
var inputField = $('.input-default:visible');
let memberType = $("#member-type").val();
let department = $("#department").val();
let vehicle = $("#vehicle").val();
// Periksa apakah input tersebut kosong
if (inputField.length === 0 || inputField.val().trim() === '') {
// Jika kosong, tampilkan alert
alert('Harap masukkan ' + (option === 'nopol' ? 'No. Polisi' : option === 'nocard' ? 'No. Card' : option === 'nama' ? 'Nama' : option === 'notrans' ? 'No. Transaksi' : 'No. Transaksi'));
inputField.focus();
return; // Hentikan proses jika input kosong
// =========================
// VALIDASI PENCARIAN
// =========================
if (searchBy !== "" && searchValue === "") {
let label = {
nama: "Nama",
alamat: "Alamat",
nopol: "No. Polisi",
nocard: "No. Card",
notrans: "No. Transaksi",
};
alert("Harap masukkan " + (label[searchBy] || "kata kunci"));
$("#search-value").focus();
return;
}
}
console.log(option);
var formData = $('#search-form .input-default:visible').val();
console.log(formData);
// =========================
// DEBUG
// =========================
console.log({
searchBy,
searchValue,
memberType,
department,
vehicle,
});
// =========================
// AJAX
// =========================
$.ajax({
url: url,
type: "POST",
data: $('#search-form').serialize(),
headers: {'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')},
dataType: 'json',
data: $("#search-form").serialize(),
headers: { "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content") },
dataType: "json",
beforeSend: function () {
$("#search-button").prop("disabled", true).html(`
<i class="fa fa-spinner fa-spin me-2"></i>
Mencari...
`);
},
success: function (response) {
var tbody = $('#example tbody');
var tbody = $("#example tbody");
tbody.empty();
if (response.length !== 0) {
@@ -98,16 +125,16 @@ $(document).ready(function () {
data-awal="${element.awal}"
data-akhir="${element.akhir}"
>
${rowIndex === 0 ? `<td rowspan="${rowspan}" class="p2" style="text-align: center;">${index + 1}</td>` : ''}
${rowIndex === 0 ? `<td rowspan="${rowspan}" class="p2" style="padding: 2px;">${element.notrans}</td>` : ''}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${element.nama}</td>` : ''}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${element.alamat}</td>` : ''}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${element.telepon}</td>` : ''}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${element.jmember}</td>` : ''}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${element.dept}</td>` : ''}
${rowIndex === 0 ? `<td rowspan="${rowspan}" width="200" style="text-align: center; padding: 2px;">${element.awal} <br> ${element.akhir}</td>` : ''}
${rowIndex === 0 ? `<td rowspan="${rowspan}" class="p-2" style="padding: 2px;"> ${[...new Set(rows.map(r => `${r.nopol || 'No. Polisi tidak ada'} (${r.vehicle || ''})`))].join('<br>')} </td>` : ''}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${[...new Set(rows.map(r => r.no_card || 'Tidak ada kartu'))].join('<br>')}</td>` : ''}
${rowIndex === 0 ? `<td rowspan="${rowspan}" class="p2" style="text-align: center;">${index + 1}</td>` : ""}
${rowIndex === 0 ? `<td rowspan="${rowspan}" class="p2" style="padding: 2px;">${element.notrans}</td>` : ""}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${element.nama}</td>` : ""}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${element.alamat}</td>` : ""}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${element.telepon}</td>` : ""}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${element.jmember}</td>` : ""}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${element.dept}</td>` : ""}
${rowIndex === 0 ? `<td rowspan="${rowspan}" width="200" style="text-align: center; padding: 2px;">${element.awal} <br> ${element.akhir}</td>` : ""}
${rowIndex === 0 ? `<td rowspan="${rowspan}" class="p-2" style="padding: 2px;"> ${[...new Set(rows.map((r) => `${r.nopol || "No. Polisi tidak ada"} (${r.vehicle || ""})`))].join("<br>")} </td>` : ""}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${[...new Set(rows.map((r) => r.no_card || "Tidak ada kartu"))].join("<br>")}</td>` : ""}
</tr>
`;
// ${rowIndex === 0 ? `<td rowspan="${rowspan}" class="p-2" style="text-align: center;">
@@ -120,12 +147,13 @@ $(document).ready(function () {
});
} else {
alert("Data tidak ditemukan.");
$('#example tbody').html(
$("#example tbody").html(
`<tr>
<td colspan="11" style="text-align: center;"><p class="text-danger mt-3">Tidak ditemukan data</p></td>
<tr>`);
<tr>`,
);
}
}
})
})
},
});
});
});
+12 -6
View File
@@ -243,6 +243,8 @@ $(document).ready(function () {
cardNumber: cardCollect,
};
console.log("FORM ADD:", allData);
if ($(".tambah-member").valid()) {
$.ajax({
url: $("#add-member").attr("action"),
@@ -250,23 +252,27 @@ $(document).ready(function () {
data: allData,
headers: { "X-CSRF-token": $('meta[name="csrf-token"]').attr("content") },
beforeSend: function () {
tombol.attr("disabled", true);
// tombol.attr("disabled", true);
},
success: function (r) {
// alert('Data berhasil dikirim');
$("#modal-member").modal("hide");
if (r.response == true) {
$("#modal-member").modal("hide");
Swal.fire({
title: "Berhasil!!",
text: "Member anda telah terdaftar !!",
text: "Member berhasil di daftarkan.. !!",
timer: 5000,
showConfirmButton: !1,
// showConfirmButton: !1,
}).then(function () {
// Reload halaman setelah SweetAlert ditutup
location.reload(); // Ini akan me-reload halaman
// location.reload(); // Ini akan me-reload halaman
});
} else if (r.response == false) {
Swal.fire("Oops...", "Terjadi kendala !!", "silahkan hubungi admin", "error");
Swal.fire({
icon: "error",
title: "Oops...",
text: r.message || "Terjadi kendala, silakan hubungi admin.",
});
}
console.log(r);
},
+47 -30
View File
@@ -1,5 +1,13 @@
$(document).ready(function () {
// Pastikan setiap request AJAX non-GET membawa token CSRF terbaru.
// Mencegah 419 "CSRF token mismatch" walau ada call yang lupa kirim header.
$(document).ajaxSend(function (event, jqxhr, settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type || '')) {
jqxhr.setRequestHeader('X-CSRF-TOKEN', $('meta[name="csrf-token"]').attr('content'));
}
});
$.validator.addClassRules('no-spinner-time', {
required: true,
min: 1
@@ -30,14 +38,10 @@ $(document).ready(function () {
let nama = $(this).data('nama');
console.log(id);
let url = "{{ route('vehicle-price.show',':id') }}";
url = url.replace(':id', id);
$.ajax({
url: '/setup/master/price/' + id,
type: 'GET',
success: function (r) {
console.log(r);
success: function (r) { console.log(r);
$('#id_mobil').val(r.id_mobil);
$('.title').text('Ubah Tarif Kendaraan ' + nama);
$('#waktu_gratis').val(r.waktu_gratis); // waktu kurang dari n pertama
@@ -53,35 +57,33 @@ $(document).ready(function () {
$('#tarif').val(r.tarif); // tarif berikut nya
$('#waktu_tarif').val(r.waktu_tarif); // per n menit
$('#maksimum').val(r.maksimum);
if(r.time_base == 1) {
$('#maks_cond').val(r.time_base_maks).show();
$('#maksimum-cond').val(0).trigger('change');
$('.maks_cond_day').hide();
$('#maks_cond_day').hide();
} else {
$('#maks_cond').hide();
$('#maksimum-cond').val(1).trigger('change');
$('.maks_cond_day').show();
$('#maks_cond_day').val(r.min_per_hari).show();
}
$('#maksimum_mode').val(r.time_base == 1 ? 'hour' : 'day');
$('#maksimum_hour').val(r.time_base_maks || 24);
$('#maksimum_day_minute').val(r.min_per_hari || 0);
$('#maksimum_mode').trigger('change');
$('#denda').val(r.denda);
$('#denda_member').val(r.denda_member);
$('#denda_only_casual').prop('checked', Number(r.denda_only_casual) === 1);
$('#denda_only').prop('checked', Number(r.denda_only) === 1);
$('#asuransi_incl').val(r.asuransi_incl || 0);
bootstrap.Tab.getOrCreateInstance(document.querySelector('#modal-vehicle .nav-link[href="#tarif"]')).show();
},
error: function (xhr) {
Swal.fire({icon: 'error', title: 'Gagal memuat tarif', text: xhr.responseJSON?.message || 'Data tarif tidak ditemukan.'});
}
});
});
$('#maksimum-cond').on('change', function() {
let selectMaxCond = $(this).val();
if (selectMaxCond == 0 ) {
$('#maks_cond').show().val(24);
$('#maksimum_mode').on('change', function() {
let mode = $(this).val();
if (mode === 'hour') {
$('#maksimum_hour').show();
$('.maks_cond_day').hide();
$('#maks_cond_day').val(1).hide();
} else if (selectMaxCond == 1) {
$('#maks_cond').hide();
$('.maks_cond_day').show();
$('#maks_cond_day').show().val(60);
$('#maksimum_day_minute').hide();
} else {
$('#maksimum_hour').hide();
$('.maks_cond_day').show();
$('#maksimum_day_minute').show();
}
});
@@ -91,11 +93,16 @@ $(document).ready(function () {
return false;
}
let id = $('#id_mobil').val();
if (!id) {
Swal.fire({icon: 'warning', title: 'ID kendaraan kosong', text: 'Tutup modal lalu klik tombol Tarif lagi.'});
return false;
}
let vehicleRate = $('#show-tarif').serialize();
let id = $('#id_mobil').val();
let url = $(this).data('url');
url = url.replace(':id', id);
url = url.replace(':id', encodeURIComponent(id));
console.log(url);
$.ajax({
@@ -106,9 +113,19 @@ $(document).ready(function () {
},
data: vehicleRate,
success: function(r) {
console.log(r);
if (!r.status) {
Swal.fire({icon: 'info', title: r.message || 'Tidak ada perubahan data'});
return;
}
$('#modal-vehicle').modal('hide');
Swal.fire({icon: 'success', title: 'Tarif diperbarui', timer: 1500, showConfirmButton: false});
},
error: function(xhr) {
if (xhr.status === 419) {
Swal.fire({icon: 'warning', title: 'Sesi berakhir', text: 'Token CSRF kedaluwarsa. Halaman akan dimuat ulang untuk token baru.', confirmButtonText: 'Muat ulang'}).then(() => window.location.reload());
return;
}
Swal.fire({icon: 'error', title: 'Gagal memperbarui tarif', text: xhr.responseJSON?.message || 'Silakan periksa kembali data tarif.'});
}
});
});
+115 -71
View File
@@ -1,74 +1,101 @@
$(document).ready(function () {
$(document).on('change', '.form-select', function () {
if ($('option:selected', this).val() == '') {
$(".nama").css('display', 'none');
$(".nopol").css('display', 'none');
$(".nocard").css('display', 'none');
$(".notrans").css('display', 'none');
$(document).on("change", ".form-select", function () {
if ($("option:selected", this).val() == "") {
$(".nama").css("display", "none");
$(".nopol").css("display", "none");
$(".nocard").css("display", "none");
$(".notrans").css("display", "none");
// $(".site").val('');
} else if ($('option:selected', this).val() == 'nama') {
$(".nama").css('display', 'block');
$(".nopol").css('display', 'none');
$(".nocard").css('display', 'none');
$(".notrans").css('display', 'none');
$(".input-default").val('');
} else if ($('option:selected', this).val() == 'nopol') {
$(".nama").css('display', 'none');
$(".nopol").css('display', 'block');
$(".nocard").css('display', 'none');
$(".notrans").css('display', 'none');
$(".input-default").val('');
} else if ($('option:selected', this).val() == 'nocard') {
$(".nama").css('display', 'none');
$(".nopol").css('display', 'none');
$(".nocard").css('display', 'block');
$(".notrans").css('display', 'none');
$(".input-default").val('');
} else if ($('option:selected', this).val() == 'notrans') {
$(".nama").css('display', 'none');
$(".nopol").css('display', 'none');
$(".nocard").css('display', 'none');
$(".notrans").css('display', 'block');
$(".input-default").val('');
} else if ($("option:selected", this).val() == "nama") {
$(".nama").css("display", "block");
$(".nopol").css("display", "none");
$(".nocard").css("display", "none");
$(".notrans").css("display", "none");
$(".input-default").val("");
} else if ($("option:selected", this).val() == "nopol") {
$(".nama").css("display", "none");
$(".nopol").css("display", "block");
$(".nocard").css("display", "none");
$(".notrans").css("display", "none");
$(".input-default").val("");
} else if ($("option:selected", this).val() == "nocard") {
$(".nama").css("display", "none");
$(".nopol").css("display", "none");
$(".nocard").css("display", "block");
$(".notrans").css("display", "none");
$(".input-default").val("");
} else if ($("option:selected", this).val() == "notrans") {
$(".nama").css("display", "none");
$(".nopol").css("display", "none");
$(".nocard").css("display", "none");
$(".notrans").css("display", "block");
$(".input-default").val("");
} else {
$(".position").css('display', 'none');
$(".level-area").css('display', 'none');
$(".input-default").val('');
$(".position").css("display", "none");
$(".level-area").css("display", "none");
$(".input-default").val("");
}
});
var url = $('#example').data('url');
var url = $("#example").data("url");
$('#search-button').on('click', function () {
// Ambil nilai dari selected option
var option = $('.select-option').val();
$("#search-button").on("click", function () {
let searchBy = $("#search-by").val();
let searchValue = $("#search-value").val().trim();
// Cek jika option yang dipilih bukan 'Pencarian Data...' yang berarti pilihan valid
if (option && option !== '') {
var inputField = $('.input-default:visible');
let memberType = $("#member-type").val();
let department = $("#department").val();
let vehicle = $("#vehicle").val();
let isActive = $("#isActive").is(":checked") ? 1 : 0;
// Periksa apakah input tersebut kosong
if (inputField.length === 0 || inputField.val().trim() === '') {
// Jika kosong, tampilkan alert
alert('Harap masukkan ' + (option === 'nopol' ? 'No. Polisi' : option === 'nocard' ? 'No. Card' : option === 'nama' ? 'Nama' : option === 'notrans' ? 'No. Transaksi' : 'No. Transaksi'));
inputField.focus();
return; // Hentikan proses jika input kosong
// =========================
// VALIDASI PENCARIAN
// =========================
if (searchBy !== "" && searchValue === "") {
let label = {
nama: "Nama",
alamat: "Alamat",
nopol: "No. Polisi",
nocard: "No. Card",
notrans: "No. Transaksi",
};
let message = "Harap masukkan " + (label[searchBy] || "kata kunci");
// Highlight input
$("#search-value").addClass("is-invalid").focus();
// Tampilkan pesan
$("#search-status").removeClass("text-success").addClass("text-danger").text(message);
$("#search-value").focus();
return;
}
}
console.log(option);
var formData = $('#search-form .input-default:visible').val();
console.log(formData);
let searchData = {
searchBy: searchBy,
searchValue: searchValue,
memberType: memberType,
department: department,
vehicle: vehicle,
isActive: isActive,
};
// =========================
// AJAX
// =========================
$.ajax({
url: url,
type: "POST",
data: $('#search-form').serialize(),
headers: {'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')},
dataType: 'json',
// data: $("#search-form").serialize(),
data: searchData,
headers: { "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content") },
dataType: "json",
success: function (response) {
var tbody = $('#example tbody');
var tbody = $("#example tbody");
tbody.empty();
if (response.length !== 0) {
@@ -98,16 +125,16 @@ $(document).ready(function () {
data-awal="${element.awal}"
data-akhir="${element.akhir}"
>
${rowIndex === 0 ? `<td rowspan="${rowspan}" class="p2" style="text-align: center;">${index + 1}</td>` : ''}
${rowIndex === 0 ? `<td rowspan="${rowspan}" class="p2" style="padding: 2px;">${element.notrans}</td>` : ''}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${element.nama}</td>` : ''}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${element.alamat}</td>` : ''}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${element.telepon}</td>` : ''}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${element.jmember}</td>` : ''}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${element.dept}</td>` : ''}
${rowIndex === 0 ? `<td rowspan="${rowspan}" width="200" style="text-align: center; padding: 2px;">${element.awal} <br> ${element.akhir}</td>` : ''}
${rowIndex === 0 ? `<td rowspan="${rowspan}" class="p-2" style="padding: 2px;"> ${[...new Set(rows.map(r => `${r.nopol || 'No. Polisi tidak ada'} (${r.vehicle || ''})`))].join('<br>')} </td>` : ''}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${[...new Set(rows.map(r => r.no_card || 'Tidak ada kartu'))].join('<br>')}</td>` : ''}
${rowIndex === 0 ? `<td rowspan="${rowspan}" class="p2 is-invalid" style="text-align: center;" width="3%">${index + 1}</td>` : ""}
${rowIndex === 0 ? oldAdmin(notrans, rowspan) : ""}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;" width="8%">${element.nama}</td>` : ""}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;" width="10%">${element.alamat}</td>` : ""}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${element.telepon}</td>` : ""}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${element.jmember}</td>` : ""}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="padding: 2px;">${element.dept}</td>` : ""}
${rowIndex === 0 ? `<td rowspan="${rowspan}" width="200" style="text-align: center; padding: 2px;" width="11%">${element.awal} <br> ${element.akhir}</td>` : ""}
${rowIndex === 0 ? `<td rowspan="${rowspan}" class="p-2" style="padding: 2px;" width="10%"> ${[...new Set(rows.map((r) => `${r.nopol || "No. Polisi tidak ada"} (${r.vehicle || ""})`))].join("<br>")} </td>` : ""}
${rowIndex === 0 ? `<td rowspan="${rowspan}" style="text-align: center; padding: 2px;">${[...new Set(rows.map((r) => r.no_card || "Tidak ada kartu"))].join("<br>")}</td>` : ""}
</tr>
`;
// ${rowIndex === 0 ? `<td rowspan="${rowspan}" class="p-2" style="text-align: center;">
@@ -119,13 +146,30 @@ $(document).ready(function () {
});
});
} else {
alert("Data tidak ditemukan.");
$('#example tbody').html(
// alert("Data tidak ditemukan.");
$("#example tbody").html(
`<tr>
<td colspan="11" style="text-align: center;"><p class="text-danger mt-3">Tidak ditemukan data</p></td>
<tr>`);
<tr>`,
);
}
},
});
});
function oldAdmin(value, rowSpan) {
let hasSlash = value.includes("/");
if (hasSlash) {
return `<td rowspan="${rowSpan}" class="p2 text-danger" style="padding: 2px;" width="17%">
${value} <i class="mdi mdi-help-circle-outline ms-1"
data-bs-toggle="tooltip"
data-bs-placement="bottom"
title="Gunakan manufacture versi lama">
</i>
</td>`;
}
return `<td rowspan="${rowSpan}" class="p2" style="padding: 2px;" width="17%">
${value}
</td>`;
}
}
})
})
});
+2 -2
View File
@@ -255,7 +255,7 @@ $(document).ready(function () {
success: function (r) {
// alert('Data berhasil dikirim');
$("#modal-member").modal("hide");
if (r.response == true) {
if (r.status == true) {
Swal.fire({
title: "Berhasil!!",
text: "Member anda telah terdaftar !!",
@@ -265,7 +265,7 @@ $(document).ready(function () {
// Reload halaman setelah SweetAlert ditutup
location.reload(); // Ini akan me-reload halaman
});
} else if (r.response == false) {
} else if (r.status == false) {
Swal.fire("Oops...", "Terjadi kendala !!", "silahkan hubungi admin", "error");
}
console.log(r);
+235
View File
@@ -0,0 +1,235 @@
@extends('layouts.master')
@section('styles')
<link href="{{ asset('vendor/datatables/css/jquery.dataTables.min.css') }}" rel="stylesheet">
<style>
.dev-card { text-align: center; padding: 14px 8px; border-radius: 8px; }
.dev-id { font-size: 22px; font-weight: 800; }
.badge-status { font-size: 12px; padding: 4px 10px; border-radius: 20px; }
.st-idle { background: #e8f5e9; color: #2e7d32; }
.st-calling { background: #fff3e0; color: #ef6c00; animation: blink 1s infinite; }
.st-active { background: #e3f2fd; color: #1565c0; }
.st-busy { background: #fce4ec; color: #c62828; }
.st-offline { background: #eeeeee; color: #616161; }
@keyframes blink { 50% { opacity: .45; } }
.queue-card { border-left: 5px solid #fb8c00; }
.queue-card.active-session { border-left-color: #1e88e5; }
.timer { font-variant-numeric: tabular-nums; font-weight: 700; }
#talkHint kbd { background: #333; color: #fff; border-radius: 4px; padding: 1px 7px; }
</style>
@endsection
@section('content')
<div class="container-fluid page-body-wrapper">
<div class="main-panel">
<div class="content-wrapper">
<div class="row mb-2">
<div class="col-md-8">
<h4 class="font-weight-bold">Intercom Pusat <small class="text-muted">— local network only</small></h4>
<p class="text-muted mb-0">Client ketok via 1 tombol &rarr; masuk antrean &rarr; operator TERIMA &rarr; sesi private (client lain tidak dengar).</p>
</div>
<div class="col-md-4 text-end">
<span id="srvStatus" class="badge badge-secondary">menghubungkan…</span>
<button class="btn btn-sm btn-light border ml-2" onclick="loadAll()">⟳ Refresh</button>
</div>
</div>
<div class="row">
{{-- ANTREAN --}}
<div class="col-md-5 grid-margin stretch-card">
<div class="card">
<div class="card-body">
<h5>Antrean <span id="queueCount" class="badge badge-warning">0</span></h5>
<p class="text-muted small">Bunyi <i>ting</i> tiap ada request baru. Klik TERIMA untuk buka sesi private.</p>
<div id="queueList"><p class="text-muted">Tidak ada antrean.</p></div>
</div>
</div>
</div>
{{-- SESI AKTIF --}}
<div class="col-md-7 grid-margin stretch-card">
<div class="card">
<div class="card-body">
<h5>Sesi Aktif</h5>
<div id="activeBox"><p class="text-muted">Belum ada sesi. Terima salah satu antrean.</p></div>
<div id="talkHint" class="alert alert-info mt-3 mb-0">
Voice tetap lewat hardware/headset via Go relay UDP :5000.
Dashboard ini <b>kontrolnya</b> (terima / end / panggil).
Tombol fisik <kbd>TALK</kbd> = tahan buat ngomong (PTT half-duplex).
</div>
</div>
</div>
</div>
</div>
{{-- STATUS SEMUA UNIT --}}
<div class="row">
<div class="col-md-12 grid-margin stretch-card">
<div class="card">
<div class="card-body">
<h5>Status Unit</h5>
<div id="deviceGrid" class="row"></div>
</div>
</div>
</div>
</div>
{{-- RIWAYAT --}}
<div class="row">
<div class="col-md-12 grid-margin stretch-card">
<div class="card">
<div class="card-body">
<h5>Riwayat 50 terakhir</h5>
<div class="table-responsive">
<table class="table table-striped" id="historyTable">
<thead>
<tr><th>ID</th><th>Unit</th><th>Arah</th><th>Status</th><th>Diminta</th><th>Durasi</th><th>Rekaman</th></tr>
</thead>
<tbody id="historyBody"><tr><td colspan="7" class="text-muted">Memuat…</td></tr></tbody>
</table>
</div>
<p class="text-muted small mt-2">Rekaman masih raw PCM 16kHz mono — klik unduh lalu putar: <code>ffplay -f s16le -ar 16000 file.raw</code></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('javascript')
<script>
const API = '/api/v1/intercom';
// Helper tanpa jQuery (layouts.master tidak selalu expose $ global)
function el(id) { return document.getElementById(id); }
function setBadge(ok, txt) {
const b = el('srvStatus');
b.setAttribute('class', 'badge ' + (ok ? 'badge-success' : 'badge-danger'));
b.textContent = txt;
}
async function fetchJSON(url, opts) {
const r = await fetch(url, opts);
if (!r.ok) throw new Error('HTTP ' + r.status + ' @ ' + url);
return r.json();
}
let seenWaiting = new Set();
let audioCtx = null;
function beep() {
try {
audioCtx = audioCtx || new (window.AudioContext || window.webkitAudioContext)();
const o = audioCtx.createOscillator(), g = audioCtx.createGain();
o.connect(g); g.connect(audioCtx.destination);
o.frequency.value = 880; o.type = 'sine';
g.gain.setValueAtTime(0.25, audioCtx.currentTime);
o.start(); o.stop(audioCtx.currentTime + 0.35);
} catch (e) {}
}
function elapsed(ts) {
if (!ts) return '-';
const s = Math.max(0, Math.floor((Date.now() - new Date(ts).getTime()) / 1000));
const m = String(Math.floor(s / 60)).padStart(2, '0');
return m + ':' + String(s % 60).padStart(2, '0');
}
function badge(st) {
const map = { idle: 'st-idle', calling: 'st-calling', waiting: 'st-calling', active: 'st-active', busy: 'st-busy', offline: 'st-offline', ended: 'st-offline', missed: 'st-busy' };
return '<span class="badge-status ' + (map[st] || 'st-offline') + '">' + (st || '-').toUpperCase() + '</span>';
}
async function loadQueue() {
try {
const q = await fetchJSON(API + '/queue');
setBadge(true, '● online');
const waiting = q.filter(x => x.status === 'waiting');
const active = q.filter(x => x.status === 'active');
el('queueCount').textContent = waiting.length;
// bunyi kalau ada waiting baru
waiting.forEach(w => { if (!seenWaiting.has(w.id)) { seenWaiting.add(w.id); beep(); } });
el('queueList').innerHTML = waiting.length ? '' : '<p class="text-muted">Tidak ada antrean.</p>';
waiting.forEach(w => {
el('queueList').insertAdjacentHTML('beforeend',
'<div class="card queue-card mb-2"><div class="card-body py-2 d-flex justify-content-between align-items-center">' +
'<div><b>' + w.device_id + '</b> <span class="timer">' + elapsed(w.requested_at) + '</span><br>' +
'<small class="text-muted">' + (w.arah === 'out' ? '⬅ pusat memanggil' : '➡ client mengetok') + ' • #' + w.id + '</small></div>' +
'<button class="btn btn-sm btn-warning" onclick="acceptCall(' + w.id + ')">TERIMA</button>' +
'</div></div>'
);
});
if (active.length) {
const a = active[0];
el('activeBox').innerHTML =
'<div class="card queue-card active-session"><div class="card-body">' +
'<h3>' + a.device_id + ' <span class="timer" id="activeTimer">' + elapsed(a.started_at || a.requested_at) + '</span></h3>' +
'<p class="text-muted mb-2">Sesi #' + a.id + ' • private, unit lain tidak dengar</p>' +
'<button class="btn btn-danger" onclick="endCall(' + a.id + ')">END</button> ' +
'<span class="text-muted small ml-2">atau tombol END fisik di box pusat</span>' +
'</div></div>';
} else {
el('activeBox').innerHTML = '<p class="text-muted">Belum ada sesi. Terima salah satu antrean.</p>';
}
} catch (e) {
setBadge(false, '● ' + e.message);
}
}
async function loadDevices() {
try {
const devs = await fetchJSON(API + '/devices');
el('deviceGrid').innerHTML = '';
devs.forEach(d => {
el('deviceGrid').insertAdjacentHTML('beforeend',
'<div class="col-md-2 col-6 mb-2"><div class="border dev-card">' +
'<div class="dev-id">' + d.device_id + '</div>' +
'<div class="small text-muted">' + (d.nama || '') + '</div>' +
'<div class="my-1">' + badge(d.status) + '</div>' +
'<button class="btn btn-sm btn-primary btn-block mt-1" onclick="callDevice(\'' + d.device_id + '\')">PANGGIL</button>' +
'</div></div>'
);
});
if (!devs.length) el('deviceGrid').innerHTML = '<p class="text-muted">Belum ada device. Seed C1–C5 + ADMIN dulu.</p>';
} catch (e) {}
}
async function loadHistory() {
try {
const h = await fetchJSON(API + '/history?limit=50');
el('historyBody').innerHTML = '';
h.forEach(r => {
const rec = r.recording_path
? '<a href="/intercom/recording/' + r.id + '" class="btn btn-sm btn-light border">⬇ unduh</a>'
: '<span class="text-muted">-</span>';
el('historyBody').insertAdjacentHTML('beforeend', '<tr><td>#' + r.id + '</td><td><b>' + r.device_id + '</b></td><td>' + r.arah + '</td><td>' + badge(r.status) + '</td><td class="small">' + (r.requested_at || '-') + '</td><td>' + (r.durasi_detik || 0) + 's</td><td>' + rec + '</td></tr>');
});
} catch (e) {}
}
async function acceptCall(id) {
await fetch(API + '/accept', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ call_id: id }) });
loadAll();
}
async function endCall(id) {
await fetch(API + '/end', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ call_id: id, ended_by: 'admin' }) });
loadAll();
}
// Panggil manual pusat -> client: buat request arah out lalu langsung accept (sesi private langsung kebuka)
async function callDevice(dev) {
if (dev === 'ADMIN') return;
const r = await (await fetch(API + '/request', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ device_id: dev, arah: 'out' }) })).json();
if (r.call_id) await fetch(API + '/accept', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ call_id: r.call_id }) });
loadAll();
}
function loadAll() { loadQueue(); loadDevices(); loadHistory(); }
loadAll();
setInterval(loadQueue, 1000);
setInterval(loadDevices, 2000);
setInterval(loadHistory, 5000);
</script>
@endsection
@@ -0,0 +1,379 @@
@extends('layouts.master')
@section('styles')
<link rel="stylesheet" href="https://cdn.datatables.net/1.13.8/css/jquery.dataTables.min.css">
<style>
thead .th {
align-text: center;
}
</style>
@endsection
@section('content')
<div class="container-fluid">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<h4 class="mb-1">Audit Report</h4>
<small class="text-muted">
Transaksi dengan status transaksi bermasalah
</small>
</div>
</div>
{{-- FILTER --}}
<div class="card">
<div class="card-body">
{{-- FILTER --}}
<div class="row g-3 mb-3">
<div class="col-md-3">
<label class="form-label">
Tanggal Mulai
</label>
<input
type="date"
id="start_date"
class="form-control"
value="{{ date('Y-m-d') }}"
>
</div>
<div class="col-md-3">
<label class="form-label">
Tanggal Akhir
</label>
<input
type="date"
id="end_date"
class="form-control"
value="{{ date('Y-m-d') }}"
>
</div>
<div class="col-md-2">
<label class="form-label">
Status
</label>
<select
id="status_transaksi"
class="form-select"
>
<option value="0">Semua</option>
<option value="-1">Pembatalan</option>
<option value="-2">Maintenance</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label">
Alasan
</label>
<select
id="filter_alasan"
class="form-select"
>
<option value="1">
Batal
</option>
<option value="0">
Semua
</option>
</select>
</div>
<div class="col-md-2 d-flex align-items-end">
<button
type="button"
class="btn btn-primary w-100"
id="btn-search"
>
<i class="fas fa-search"></i>
Cari
</button>
</div>
</div>
{{-- SUMMARY --}}
<div class="row mb-3">
<div class="col-md-3">
<div class="border rounded p-3">
<small>Total Transaksi</small>
<h4 id="total-transaksi">
0
</h4>
</div>
</div>
<div class="col-md-3">
<div class="border rounded p-3">
<small>Total Bayar</small>
<h4 id="total-bayar">
Rp 0
</h4>
</div>
</div>
</div>
{{-- TABLE --}}
<div class="table-responsive">
<table
class="table table-bordered table-hover"
id="audit-table"
>
<thead>
<tr>
<th>ID</th>
<th>Nomer Polisi</th>
<th>Waktu Masuk</th>
<th>Waktu Keluar</th>
<th>Status</th>
<th>Bayar Keluar</th>
<th>Alasan</th>
<th>Jenis Pembayaran</th>
<th>Petugas</th>
</tr>
</thead>
<tbody id="audit-table-body">
{{-- kosong saat pertama dibuka --}}
</tbody>
</table>
</div>
</div>
</div>
</div>
@endsection
@section('javascript')
<script src="https://cdn.datatables.net/1.13.8/js/jquery.dataTables.min.js"></script>
<script>
$(function () {
$('#btn-search').on('click', function () {
const btn = $(this);
const startDate = $('#start_date').val();
const endDate = $('#end_date').val();
const status = $('#status_transaksi').val();
const alasan = $('#filter_alasan').val();
if (!startDate || !endDate) {
alert('Tanggal harus diisi');
return;
}
if (startDate > endDate) {
alert(
'Tanggal mulai tidak boleh lebih besar dari tanggal akhir'
);
return;
}
$.ajax({
url: "{{ route('audit.data') }}",
type: "GET",
data: {
start_date: startDate,
end_date: endDate,
status_transaksi: status,
filter_alasan: alasan
},
dataType: "json",
beforeSend: function () {
btn.prop('disabled', true);
$('#audit-table-body').html(`
<tr>
<td colspan="8"
class="text-center">
Memuat data...
</td>
</tr>
`);
},
success: function (response) {
if (!response.response) {
return;
}
const rows = response.data;
$('#audit-table-body').empty();
if (rows.length === 0) {
$('#audit-table-body').html(`
<tr>
<td colspan="8"
class="text-center">
Tidak ada data
</td>
</tr>
`);
} else {
rows.forEach(function (row) {
$('#audit-table-body').append(`
<tr>
<td class="text-center">
${row.id ?? '-'}
</td>
<td class="text-center">
${row.no_pol ?? '-'}
</td>
<td class="text-center">
${row.waktu_masuk ?? '-'}
</td class="text-center">
<td class="text-center">
${row.waktu_keluar ?? '-'}
</td>
<td class="text-center">
<span class="badge bg-danger">
${row.status_transaksi ?? '-'}
</span>
</td>
<td class="text-end">
Rp ${Number(
row.bayar_keluar ?? 0
).toLocaleString('id-ID')}
</td>
<td class="text-center">
${row.alasan ?? '-'}
</td>
<td class="text-center">
${row.cara_bayar == 2 ? 'Tunai' : 'Non Tunai'}
</td>
<td class="text-center">
${row.operator ?? '-'}
</td>
</tr>
`);
});
}
// SUMMARY
$('#total-transaksi').text(
Number(
response.summary.total_transaksi
).toLocaleString('id-ID')
);
$('#total-bayar').text(
'Rp ' +
Number(
response.summary.total_bayar
).toLocaleString('id-ID')
);
},
error: function (xhr) {
console.error(xhr.responseText);
$('#audit-table-body').html(`
<tr>
<td colspan="8"
class="text-center text-danger">
Gagal mengambil data
</td>
</tr>
`);
},
complete: function () {
btn.prop('disabled', false);
}
});
});
function payment(value) {
switch (value) {
case 2:
return 'Tunai';
case 3:
return 'Non Tunai';
default:
return 'Jenis Pembayaran Tidak ditemukan'
}
};
function vehicleStatus(value) {
switch (value) {
case 0:
return 'Sudah Keluar';
case 1:
return 'Masih didalam';
case -2:
return 'Maintenance';
default:
return 'Tidak Ada'
}
}
});
</script>
@endsection
@@ -0,0 +1,143 @@
@extends('layouts.master') {{-- sesuaikan dengan layout master project kamu --}}
@section('content')
<div class="container-fluid py-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h4 class="mb-0">Data Stasiun Kerja</h4>
<button type="button" class="btn btn-primary" id="btnTambah">
+ Tambah Stasiun Kerja
</button>
</div>
<div class="table-responsive">
<table class="table table-bordered table-hover align-middle" id="tabelStasiunKerja">
<thead class="table-light">
<tr>
<th>Kode</th>
<th>Nama</th>
<th>Margin</th>
<th>SPV</th>
<th>Target</th>
<th>Lokasi</th>
<th>Lantai</th>
<th>Status</th>
<th>Alamat</th>
<th>Bank</th>
<th>Telp</th>
<th>Kode Akses</th>
<th style="width: 130px;">Aksi</th>
</tr>
</thead>
<tbody id="tbodyStasiunKerja">
{{-- diisi lewat JS --}}
</tbody>
</table>
</div>
</div>
{{-- ===================== MODAL FORM ===================== --}}
<div class="modal fade" id="modalStasiunKerja" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<form id="formStasiunKerja">
<div class="modal-header">
<h5 class="modal-title" id="modalStasiunKerjaTitle">Tambah Stasiun Kerja</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="inputMode" value="create">
<input type="hidden" id="inputKodeOriginal" value="">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">Kode <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="inputKode" maxlength="50" required>
<div class="invalid-feedback" data-error-for="kode"></div>
</div>
<div class="col-md-8">
<label class="form-label">Nama <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="inputNama" maxlength="255" required>
<div class="invalid-feedback" data-error-for="nama"></div>
</div>
<div class="col-md-3">
<label class="form-label">Margin</label>
<input type="number" class="form-control" id="inputMargin">
<div class="invalid-feedback" data-error-for="margin"></div>
</div>
<div class="col-md-3">
<label class="form-label">Target</label>
<input type="number" class="form-control" id="inputTarget">
<div class="invalid-feedback" data-error-for="target"></div>
</div>
<div class="col-md-3">
<label class="form-label">Status</label>
<select class="form-select" id="inputStatus">
<option value="1">Aktif</option>
<option value="0">Nonaktif</option>
</select>
<div class="invalid-feedback" data-error-for="status"></div>
</div>
<div class="col-md-3">
<label class="form-label">Kode Akses</label>
<input type="text" class="form-control" id="inputKodeAkses" maxlength="5">
<div class="invalid-feedback" data-error-for="kode_akses"></div>
</div>
<div class="col-md-4">
<label class="form-label">SPV</label>
<input type="text" class="form-control" id="inputSpv" maxlength="20">
<div class="invalid-feedback" data-error-for="spv"></div>
</div>
<div class="col-md-4">
<label class="form-label">ID Lokasi</label>
<input type="text" class="form-control" id="inputIdLokasi" maxlength="10">
<div class="invalid-feedback" data-error-for="id_lokasi"></div>
</div>
<div class="col-md-4">
<label class="form-label">Lantai</label>
<input type="text" class="form-control" id="inputLantai" maxlength="4">
<div class="invalid-feedback" data-error-for="lantai"></div>
</div>
<div class="col-md-6">
<label class="form-label">Bank</label>
<input type="text" class="form-control" id="inputBank" maxlength="64">
<div class="invalid-feedback" data-error-for="bank"></div>
</div>
<div class="col-md-6">
<label class="form-label">Telp</label>
<input type="text" class="form-control" id="inputTelp" maxlength="32">
<div class="invalid-feedback" data-error-for="telp"></div>
</div>
<div class="col-md-12">
<label class="form-label">Alamat</label>
<textarea class="form-control" id="inputAlamat" rows="2" maxlength="255"></textarea>
<div class="invalid-feedback" data-error-for="alamat"></div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-primary" id="btnSimpan">Simpan</button>
</div>
</form>
</div>
</div>
</div>
@endsection
@section('javascript')
<script>
// Endpoint diambil dari Laravel route(), disuntik ke JS supaya
// tidak hardcode URL (menghindari masalah beda host/port seperti sebelumnya).
window.STASIUNKERJA_ENDPOINT = "{{ url('/setup/master/stasiunkerja') }}";
</script>
<script src="{{ asset('js/setup/stasiunkerja.js') }}"></script>
@endsection
@@ -0,0 +1,150 @@
@extends('layouts.master') {{-- sesuaikan dengan layout master project kamu --}}
@section('content')
<div class="container-fluid py-4">
<div class="d-flex justify-content-between align-items-center mb-3">
<h4 class="mb-0">Data Tarif Stiker</h4>
<button type="button" class="btn btn-primary" id="btnTambah">
+ Tambah Tarif Stiker
</button>
</div>
<div class="table-responsive">
<table class="table table-bordered table-hover align-middle" id="tabelTarifStiker">
<thead class="table-light">
<tr>
<th id="thTarifId" role="button" style="cursor:pointer; user-select:none; white-space:nowrap;">
Tarif ID <span id="sortIconTarifId">▼</span>
</th>
<th>ID Mobil</th>
<th>Jenis Langganan</th>
<th>Tarif</th>
<th>Jenis</th>
<th>Jenis Member</th>
<th>Langganan (Stasiun Kerja)</th>
<th>Online</th>
<th>Kode Akses</th>
<th style="width: 130px;">Aksi</th>
</tr>
</thead>
<tbody id="tbodyTarifStiker">
{{-- diisi lewat JS --}}
</tbody>
</table>
</div>
</div>
{{-- ===================== MODAL FORM ===================== --}}
<div class="modal fade" id="modalTarifStiker" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<form id="formTarifStiker">
<div class="modal-header">
<h5 class="modal-title" id="modalTarifStikerTitle">Tambah Tarif Stiker</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="inputMode" value="create">
<input type="hidden" id="inputTarifIdOriginal" value="">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">Tarif ID</label>
<input type="number" class="form-control" id="inputTarifId" placeholder="Kosongkan untuk auto">
<div class="form-text">Kosongkan saat tambah data baru, akan digenerate otomatis.</div>
<div class="invalid-feedback" data-error-for="tarif_id"></div>
</div>
<div class="col-md-4">
<label class="form-label">ID Mobil <span class="text-danger">*</span></label>
<input type="text" class="form-control" id="inputIdMobil" maxlength="10" required>
<div class="invalid-feedback" data-error-for="id_mobil"></div>
</div>
<div class="col-md-4">
<label class="form-label">Tarif <span class="text-danger">*</span></label>
<input type="number" step="0.01" class="form-control" id="inputTarif" required>
<div class="invalid-feedback" data-error-for="tarif"></div>
</div>
<div class="col-md-6">
<label class="form-label">Jenis Langganan <span class="text-danger">*</span></label>
<select class="form-select" id="inputJenisLangganan" required>
<option value="">-- Pilih Jenis Langganan --</option>
@foreach ($jenisLanggananList as $jl)
<option value="{{ $jl->id }}">{{ $jl->nama }}</option>
@endforeach
</select>
<div class="invalid-feedback" data-error-for="jenis_langganan"></div>
</div>
<div class="col-md-6">
<label class="form-label">Langganan (Stasiun Kerja)</label>
<select class="form-select" id="inputLangganan">
<option value="">-- Tidak terikat stasiun kerja --</option>
@foreach ($stasiunKerjaList as $sk)
<option value="{{ $sk->kode }}">{{ $sk->nama }} ({{ $sk->kode }})</option>
@endforeach
</select>
<div class="invalid-feedback" data-error-for="langganan"></div>
</div>
<div class="col-md-3">
<label class="form-label">Jenis</label>
<input type="text" class="form-control" id="inputJenis" maxlength="30">
<div class="invalid-feedback" data-error-for="jenis"></div>
</div>
<div class="col-md-3">
<label class="form-label">Jenis Member</label>
<input type="text" class="form-control" id="inputJenisMember" maxlength="50">
<div class="invalid-feedback" data-error-for="jenis_member"></div>
</div>
<div class="col-md-3">
<label class="form-label">Last Member</label>
<input type="text" class="form-control" id="inputLastMember" maxlength="5">
<div class="invalid-feedback" data-error-for="last_member"></div>
</div>
<div class="col-md-3">
<label class="form-label">Tgl Akhir</label>
<input type="text" class="form-control" id="inputTglAkhir" maxlength="2">
<div class="invalid-feedback" data-error-for="tgl_akhir"></div>
</div>
<div class="col-md-4">
<label class="form-label">Hitung Awal Bulan</label>
<input type="number" class="form-control" id="inputHitungAwalBulan">
<div class="invalid-feedback" data-error-for="hitung_awal_bulan"></div>
</div>
<div class="col-md-4">
<label class="form-label">Kode Akses</label>
<input type="text" class="form-control" id="inputKodeAkses" maxlength="5">
<div class="invalid-feedback" data-error-for="kode_akses"></div>
</div>
<div class="col-md-4 d-flex align-items-end">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="inputAvailableOnline">
<label class="form-check-label" for="inputAvailableOnline">
Tersedia Online
</label>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Batal</button>
<button type="submit" class="btn btn-primary" id="btnSimpan">Simpan</button>
</div>
</form>
</div>
</div>
</div>
@endsection
@section('javascript')
<script>
window.TARIFSTIKER_ENDPOINT = "{{ url('/setup/master/tarif-stiker') }}";
</script>
<script src="{{ asset('js/setup/tarifstiker.js') }}"></script>
@endsection
@@ -31,6 +31,11 @@ class="img-fluid logo-img"> --}}
<div class="member-info">
<div class="info-row">
<span class="label">No. Transaksi</span>
<span id="act-notrans" class="value"></span>
</div>
<div class="info-row">
<span class="label">Nama</span>
<span id="act-nama" class="value"></span>
@@ -75,6 +80,12 @@ class="img-fluid logo-img"> --}}
<div class="member-info">
<div class="info-row">
<span class="label">No. Transaksi</span>
<span id="exp-notrans" class="value"></span>
<span id="exp-notrans-value" class="value" hidden></span>
</div>
<div class="info-row">
<span class="label">Nama</span>
<span id="exp-nama" class="value"></span>
@@ -145,7 +156,7 @@ function submitNopol(){
if(!nopol) return;
fetch("/api/member/" + encodeURIComponent(nopol))
fetch("/api/memberin/" + encodeURIComponent(nopol))
.then(res => res.json())
.then(data => {
@@ -163,6 +174,7 @@ function submitNopol(){
console.log(data);
document.getElementById("form-active").style.display="block";
document.getElementById("act-notrans").innerText = data.data.notrans;
document.getElementById("act-nama").innerText = data.data.nama;
document.getElementById("act-nopol").innerText = data.data.vehicle + " - " + data.data.nopol;
document.getElementById("act-periode").innerText = formatTanggal(data.data.awal.split(' ')[0]) + " - " + formatTanggal(data.data.akhir.split(' ')[0])
@@ -179,6 +191,8 @@ function submitNopol(){
console.log(data);
document.getElementById("form-expired").style.display="block";
document.getElementById("exp-notrans").innerText = data.data.notrans.slice(0, 13);
document.getElementById("exp-notrans-value").innerText = data.data.notrans;
document.getElementById("exp-nama").innerText = data.data.nama;
document.getElementById("exp-nopol").innerText = data.data.vehicle + " - " + data.data.nopol;
document.getElementById("exp-periode").innerText = formatTanggal(data.data.awal.split(' ')[0]) + " - " + formatTanggal(data.data.akhir.split(' ')[0])
+9 -1
View File
@@ -14,6 +14,11 @@
<div class="text-center mb-3">
<h4 class="text-center mb-4">Silahkan login untuk mengakses halaman</h4>
</div>
@if ($errors->any())
<div class="alert alert-danger py-2 mb-3" role="alert">
<i class="fa fa-triangle-exclamation me-2"></i>{{ $errors->first() }}
</div>
@endif
<form method="POST" action="{{ route('login') }}">
@csrf
{{-- <div class="form-group mb-4"> --}}
@@ -28,11 +33,14 @@
</div>
<div class="mb-sm-4 mb-3 position-relative">
<label class="form-label" for="dlab-password">Password</label>
<input type="password" id="password" class="form-control" value="123456" @error('password') is-invalid @enderror name="password" required autocomplete="current-password">
<input type="password" id="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="current-password">
<span class="show-pass eye">
<i class="fa fa-eye-slash"></i>
<i class="fa fa-eye"></i>
</span>
@error('password')
<span class="invalid-feedback" role="alert"><strong>{{ $message }}</strong></span>
@enderror
</div>
<div class="row mb-0">
<div class="col-md-12 text-center">
-10
View File
@@ -426,16 +426,6 @@
</div>
</div>
<!-- content-wrapper ends -->
<!-- partial:partials/_footer.html -->
<footer class="footer">
<div class="footer-wrap">
<div class="d-sm-flex justify-content-center justify-content-sm-between">
<span class="text-muted text-center text-sm-left d-block d-sm-inline-block">Copyright © <a href="https://www.bootstrapdash.com/" target="_blank">bootstrapdash.com </a>2021</span>
<span class="float-none float-sm-right d-block mt-1 mt-sm-0 text-center">Only the best <a href="https://www.bootstrapdash.com/" target="_blank"> Bootstrap dashboard </a> templates</span>
</div>
</div>
</footer>
<!-- partial -->
</div>
<!-- main-panel ends -->
</div>
+12
View File
@@ -27,7 +27,19 @@
@yield('content')
<!-- page-body-wrapper ends -->
<!-- partial:partials/_footer.html -->
<footer class="footer">
<div class="footer-wrap">
<div class="d-sm-flex justify-content-center justify-content-sm-between">
<span class="text-muted text-center text-sm-left d-block d-sm-inline-block">Copyright © <a href="https://www.bootstrapdash.com/" target="_blank">bootstrapdash.com </a>2021</span>
<span class="float-none float-sm-right d-block mt-1 mt-sm-0 text-center">Only the best <a href="https://www.bootstrapdash.com/" target="_blank"> Bootstrap dashboard </a> templates</span>
</div>
</div>
</footer>
<!-- partial -->
<!--</div>-->
<!-- container-scroller -->
<!-- base:js -->
<script src="{{ asset('vendors/base/vendor.bundle.base.js') }}"></script>
@@ -0,0 +1,3 @@
<div>
{{-- If your happiness depends on money, you will never be happy with yourself. --}}
</div>
+70
View File
@@ -0,0 +1,70 @@
@extends('layouts.master')
@section('title', 'DHCP Reservations')
@section('content')
<div class="row">
<div class="col-md-12">
<div class="card mg-b-20">
<div class="card-header">
<div class="card-title">
<h3 class="card-title mb-0">
Lot Parking
</h3>
</div>
</div>
<div class="card-body">
{{-- Container yang akan di-refresh --}}
<div id="parking-container">
@include('livewire.partial-lot', ['parkingDataByArea' => $parkingDataByArea])
</div>
</div>
</div>
</div>
</div>
@endsection
@section('javascript')
<script>
document.addEventListener('DOMContentLoaded', function () {
const parkingContainer = document.getElementById('parking-container');
const refreshUrl = "{{ route('parking-lot.data') }}";
async function refreshParking() {
try {
const response = await fetch(refreshUrl, {
method: 'GET',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'text/html'
}
});
if (!response.ok) {
throw new Error('HTTP Error: ' + response.status);
}
const html = await response.text();
parkingContainer.innerHTML = html;
} catch (error) {
console.error(
'Gagal mengambil data parking:',
error
);
}
}
// Refresh setiap 5 detik
setInterval(refreshParking, 5000);
});
</script>
@endsection
@@ -0,0 +1,44 @@
<div>
<script>
document.addEventListener('livewire:load', function () {
// console.log('Livewire component loaded');
setInterval(function () {
@this.call('fetchParkingData');
}, 5000);
// Livewire.on('refresh', (time) => {
// console.log('Data refreshed at:', time);
// alert('Data refreshed at: ' + time);
// });
});
</script>
<div class="row">
<div class="col-md-12">
<div class="card mg-b-20">
<div class="card-header">
<div class="card-title">
<h3 class="card-title">Lot Parking</h3>
</div>
</div>
<div class="card-body">
<div class="row">
@foreach ($parkingDataByArea as $areaData)
<div class="col-md-6 card-content mb-5 px-3">
<h3 style="text-align: center;">Lantai: {{ $areaData['lantai'] }}</h3>
<div class="container mt-3">
<div class="row img-container">
@foreach ($areaData['data'] as $log)
<div class="col-md-6 img-item">
<img src="{{ $log->status == 1 ? asset('build/assets/images/users/red_car.png') : asset('build/assets/images/users/green_car.png') }}" alt="{{ $log->status == 1 ? 'Red Car' : 'Green Car' }}">
<span class="d-block">{{ $log->lot_number }}</span>
</div>
@endforeach
</div>
</div>
</div>
@endforeach
</div>
</div>
</div>
</div>
</div>
</div>
@@ -0,0 +1,42 @@
<div class="row">
@foreach ($parkingDataByArea as $areaData)
<div class="col-md-6 card-content mb-5 px-3">
<h3 class="text-center">
Lantai: {{ $areaData['lantai'] }}
</h3>
<div class="container mt-3">
<div class="row img-container">
@foreach ($areaData['data'] as $log)
<div class="col-md-6 img-item text-center mb-3">
<img
src="{{ $log->status == 1
? asset('images/lot/red_car.png')
: asset('images/lot/green_car.png') }}"
alt="{{ $log->status == 1 ? 'Red Car' : 'Green Car' }}"
>
<span class="d-block">
{{ $log->lot_number }}
</span>
</div>
@endforeach
</div>
</div>
</div>
@endforeach
</div>
+200
View File
@@ -0,0 +1,200 @@
@extends('layouts.master')
@section('title', 'Parking Space Information')
@section('styles')
<!-- Treeview Css -->
{{-- <link rel="stylesheet" href="{{asset('build/assets/libs/js-treeview/treeview.min.css')}}"> --}}
<style>
.img-container {
display: flex;
flex-wrap: wrap;
}
.img-item {
flex: 1 0 8.33%; /* 100% / 12 = 8.33% */
max-width: 8.33%;
padding: 5px;
}
.img-item img {
width: 100%;
height: auto;
/* display: block;
margin-top: 10px; */
}
.img-item span {
display: block;
margin-top: 10px; /* Atur jarak antara gambar dan teks jika diperlukan */
}
</style>
@endsection
@section('content')
<!-- PAGE-HEADER -->
<!--<div class="page-header d-flex align-items-center justify-content-between border-bottom mb-4">
<h1 class="page-title">Parking Lot Space</h1>
<div>
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="javascript:void(0);">Admin UI</a></li>
<li class="breadcrumb-item active" aria-current="page">Treeview</li>
</ol>
</div>
</div>-->
<!-- PAGE-HEADER END -->
<!-- CONTAINER -->
<div class="main-container container-fluid">
<!-- CONTAINER -->
{{-- <div class="main-container container-fluid">
<!-- ROW OPEN -->
<div class="row">
<div class="col-md-12">
<div class="card mg-b-20">
<div class="card-header">
<div class="card-title">
<h3 class="card-title">Lot Parking</h3>
</div>
</div>
<div class="card-body">
<div class="row">
<!-- Konten akan diperbarui oleh AJAX -->
</div>
</div>
</div>
</div>
</div>
<!-- ROW CLOSED -->
</div> --}}
<!-- ROW OPEN -->
<div class="row">
<div class="col-md-12">
<div class="card mg-b-20">
<div class="card-header">
<div class="card-title">
<h3 class="card-title">Lot Parking</h3>
</div>
</div>
<div class="card-body">
<div class="row" id="lot-space">
</div>
</div>
</div>
</div>
</div>
<!-- ROW CLOSED -->
{{-- <!-- ROW OPEN -->
<div class="row">
<div class="col-md-12">
<div class="card mg-b-20">
<div class="card-header">
<div class="card-title">
<h3 class="card-title">Treeview</h3>
</div>
</div>
<div class="card-body">
<div class="card-content">
<div class="row">
@foreach ($parkingDataByArea as $areaData)
<h3>Lantai: {{ $areaData['lantai'] }}</h3>
<div class="col-sm-12 col-md-12 col-lg-12 col-xl-3">
<div class="d-flex flex-wrap gap-2">
@foreach ($areaData['data'] as $log)
<div class="m-2">
@if ($log->status == 1)
<span class="avatar avatar-md bradius cover-image" data-bs-image-src="{{ asset('images/lot/red_car.png') }}"></span>
<li>Lot Number: {{ $log->lot_number }} - Status: {{ $log->status }}</li>
@else
<span class="avatar avatar-md bradius cover-image" data-bs-image-src="{{ asset('images/lot/green_car.png') }}"></span>
<li>Lot Number: {{ $log->lot_number }} - Status: {{ $log->status }}</li>
@endif
</div>
@endforeach
</div>
</div>
@endforeach
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ROW CLOSED --> --}}
</div>
<!-- CONTAINER END -->
@endsection
@section('javascript')
<!-- Tambahkan ini dalam template blade Anda -->
<script>
document.addEventListener('DOMContentLoaded', function () {
function fetchParkingData() {
fetch('{{ route("parking-lot.data") }}')
.then(response => {
if (!response.ok) {
throw new Error('HTTP Error: ' + response.status);
}
return response.json();
})
.then(data => {
const container = document.getElementById('lot-space');
container.innerHTML = '';
data.forEach(function (areaData) {
let html = `
<div class="col-md-6 card-content mb-5 px-3">
<h3 class="text-center">
Parking Space : ${areaData.lantai}
</h3>
<div class="container mt-3">
<div class="row img-container">
`;
areaData.data.forEach(function (log) {
const image = log.status == 1
? '{{ asset("images/lot/red_car.png") }}'
: '{{ asset("images/lot/green_car.png") }}';
html += `
<div class="col-md-6 img-item">
<img src="${image}" alt="${log.status == 1 ? 'Red Car' : 'Green Car'}">
<span class="d-block">${log.lot_number}</span>
</div>
`;
});
html += `
</div>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', html);
});
})
.catch(error => {
console.error('Error fetching parking data:', error);
});
}
// Load pertama kali
fetchParkingData();
// Refresh setiap 5 detik
setInterval(fetchParkingData, 5000);
});
</script>
@endsection
@@ -39,55 +39,112 @@
<div class="card-header">
</div>
<div class="card-body">
<form action="">
<div class="row align-items-end g-3">
<form id="filter-parkir" action="javascript:void(0)">
<div class="row align-items-end g-2">
<!-- CARI DATA -->
<div class="col-md-1">
<div class="form-group">
<label for="data">Cari Data</label>
<input type="text" class="form-control" id="data" placeholder="Masukkan data">
<label for="q">Cari Data</label>
<input type="text" class="form-control form-control-sm" id="q" placeholder="Keyword">
</div>
</div>
<div class="col-md-1">
<div class="form-group">
<label>&nbsp;</label> <!-- biar sejajar -->
<select class="form-control js-example-basic-single">
<label for="field">Field</label>
<select class="form-control form-control-sm js-example-basic-single" id="field">
<option value="id" selected>No. ID</option>
<option value="no_pol">No. Pol</option>
<option value="no_barcode">No. Barcode/Card</option>
<option value="no_barcode">No. Transaksi</option>
</select>
</div>
</div>
<!-- TANGGAL -->
<!-- PERIODE -->
<div class="col-md-2">
<div class="form-group">
<label for="tgl_awal">Periode Awal</label>
<input type="date" class="form-control form-control-sm" id="tgl_awal" name="tgl_awal">
</div>
</div>
<div class="col-md-2">
<div class="form-group">
<label for="tgl_akhir">Periode Akhir</label>
<input type="date" class="form-control form-control-sm" id="tgl_akhir" name="tgl_akhir">
</div>
</div>
<!-- STATUS KENDARAAN : 1 masuk, 0 keluar -->
<div class="col-md-1">
<div class="form-group">
<label for="tanggalCari">Tanggal</label>
<input type="date" class="form-control" id="tanggalCari" name="tanggal_mulai" required>
<label for="status_kendaraan">Kendaraan</label>
<select class="form-control form-control-sm js-example-basic-single" id="status_kendaraan">
<option value="" selected>Semua</option>
<option value="1">Masuk</option>
<option value="0">Keluar</option>
</select>
</div>
</div>
<!-- TOGGLE HARIAN -->
<div class="col-md-2 d-flex align-items-center gap-2">
<p class="mb-0">Harian</p>
<label class="toggle-switch toggle-switch-success mb-0">
<input type="checkbox" id="harianToggle" checked>
<span class="toggle-slider round"></span>
</label>
<!-- STATUS TRANSAKSI : 0 casual, 3 member, 32 denda -->
<div class="col-md-2">
<div class="form-group">
<label for="status_transaksi">Transaksi</label>
<select class="form-control form-control-sm js-example-basic-single" id="status_transaksi">
<option value="" selected>Semua</option>
<option value="0">Casual</option>
<option value="3">Member</option>
<option value="32">Denda</option>
</select>
</div>
</div>
<!-- AKSI -->
<div class="col-md-2 d-flex align-items-end gap-1">
<button type="submit" class="btn btn-sm btn-primary" id="btn-cari">
<i class="fa fa-search me-1"></i>Cari
</button>
<button type="button" class="btn btn-sm btn-secondary" id="btn-reset">Reset</button>
</div>
<!-- BUTTON EXPORT -->
<div class="col-md-4 text-end">
<div class="btn-list d-inline-flex gap-2">
<div class="col-md-1 text-end">
<div class="btn-list d-inline-flex gap-1">
<button type="button"
class="btn btn-icon btn-success btn-export-excel">
class="btn btn-sm btn-icon btn-success btn-export-excel" title="Export CSV">
<i class="fa fa-file-excel-o"></i>
</button>
<button type="button"
class="btn btn-icon btn-danger btn-export-pdf">
class="btn btn-sm btn-icon btn-danger btn-export-pdf" title="Cetak">
<i class="fa fa-file-pdf-o"></i>
</button>
</div>
</div>
</div>
</form>
<hr class="my-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<h5 class="card-title mb-0">Hasil Transaksi</h5>
<small class="text-muted" id="info-paging"></small>
</div>
<div class="table-responsive">
<table class="table table-bordered table-striped" id="tabel-parkir">
<thead>
<tr>
<th width="4%">No.</th>
<th>ID</th>
<th>No. Pol</th>
<th>Barcode</th>
<th>Kendaraan</th>
<th>Waktu Masuk</th>
<th>Waktu Keluar</th>
<th>Status Kendaraan</th>
<th>Status Transaksi</th>
</tr>
</thead>
<tbody id="body-parkir">
<tr><td colspan="9" class="text-center">Memuat data...</td></tr>
</tbody>
</table>
</div>
<div class="d-flex justify-content-between align-items-center mt-3">
<button class="btn btn-sm btn-outline-primary" id="btn-prev" type="button">&laquo; Sebelumnya</button>
<span class="text-muted" id="label-halaman"></span>
<button class="btn btn-sm btn-outline-primary" id="btn-next" type="button">Berikutnya &raquo;</button>
</div>
</div>
</div>
@@ -101,4 +158,107 @@ class="btn btn-icon btn-danger btn-export-pdf">
@section('javascript')
<script src="{{ asset('vendors/select2/select2.min.js') }}"></script>
<script src="{{ asset('js/select2.js') }}"></script>
<script>
$(function () {
const dataUrl = '{{ route('transaksiparkir.data', [], false) }}';
let page = 1;
const labelKendaraan = {1: 'Masuk', 0: 'Keluar'};
const labelTransaksi = {0: 'Casual', 3: 'Member', 32: 'Denda'};
function badge(text, color) {
return '<span class="badge bg-' + color + '">' + text + '</span>';
}
function statusKendaraan(v) {
if (v == 1) return badge('Masuk', 'success');
if (v == 0) return badge('Keluar', 'secondary');
return badge(v ?? '-', 'light text-dark');
}
function statusTransaksi(v) {
const t = labelTransaksi[v] !== undefined ? labelTransaksi[v] : (v ?? '-');
const c = v == 0 ? 'primary' : (v == 3 ? 'info' : (v == 32 ? 'warning' : 'light text-dark'));
return badge(t, c);
}
function esc(v) {
return (v === null || v === undefined) ? '' : String(v)
.replace(/&/g, '&amp;').replace(/</g, '&lt;')
.replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
function params(extra) {
const p = {
q: $('#q').val().trim(),
field: $('#field').val(),
tgl_awal: $('#tgl_awal').val(),
tgl_akhir: $('#tgl_akhir').val(),
status_kendaraan: $('#status_kendaraan').val(),
status_transaksi: $('#status_transaksi').val(),
page: page
};
return $.param(Object.assign(p, extra || {}));
}
function load() {
$('#body-parkir').html('<tr><td colspan="9" class="text-center">Memuat data...</td></tr>');
$.ajax({
url: dataUrl + '?' + params(),
type: 'GET',
success: function (r) {
if (!r.data.length) {
$('#body-parkir').html('<tr><td colspan="9" class="text-center">Data tidak ditemukan.</td></tr>');
} else {
let html = '';
const offset = (r.meta.current_page - 1) * r.meta.per_page;
r.data.forEach(function (row, i) {
html += '<tr>'
+ '<td class="text-center">' + (offset + i + 1) + '</td>'
+ '<td>' + esc(row.id) + '</td>'
+ '<td>' + esc(row.no_pol) + '</td>'
+ '<td>' + esc(row.no_barcode) + '</td>'
+ '<td>' + esc([row.id_kendaraan, row.nama_kendaraan].filter(Boolean).join(' - ')) + '</td>'
+ '<td>' + esc(row.waktu_masuk) + '</td>'
+ '<td>' + esc(row.waktu_keluar) + '</td>'
+ '<td class="text-center">' + statusKendaraan(row.status) + '</td>'
+ '<td class="text-center">' + statusTransaksi(row.status_transaksi) + '</td>'
+ '</tr>';
});
$('#body-parkir').html(html);
}
$('#info-paging').text('Total ' + r.meta.total + ' data');
$('#label-halaman').text('Halaman ' + r.meta.current_page + ' / ' + Math.max(r.meta.last_page, 1));
$('#btn-prev').prop('disabled', r.meta.current_page <= 1);
$('#btn-next').prop('disabled', r.meta.current_page >= r.meta.last_page);
page = r.meta.current_page;
},
error: function (xhr) {
let msg = 'Gagal memuat data.';
if (xhr.status === 422 && xhr.responseJSON?.errors) {
msg = Object.values(xhr.responseJSON.errors).flat().join(' ');
}
$('#body-parkir').html('<tr><td colspan="9" class="text-center text-danger">' + esc(msg) + '</td></tr>');
}
});
}
$('#filter-parkir').on('submit', function () { page = 1; load(); });
$('#btn-reset').on('click', function () {
$('#filter-parkir')[0].reset();
$('#field, #status_kendaraan, #status_transaksi').val('').trigger('change');
$('#field').val('id').trigger('change');
page = 1; load();
});
$('#btn-prev').on('click', function () { if (page > 1) { page--; load(); } });
$('#btn-next').on('click', function () { page++; load(); });
$('.btn-export-excel').on('click', function () {
window.location = dataUrl + '?' + params({export: 'csv'});
});
$('.btn-export-pdf').on('click', function () { window.print(); });
load();
});
</script>
@endsection
+31
View File
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>Persiapan Aplikasi | P-Solution Parking</title>
<style>
:root { --navy:#102a43; --blue:#1f6feb; --mint:#2dd4bf; --ink:#152536; --muted:#6b7b8d; --line:#dce5ef; } *{box-sizing:border-box} body{min-height:100vh;margin:0;color:var(--ink);font-family:Inter,ui-sans-serif,system-ui,sans-serif;background:radial-gradient(circle at 10% 10%,#d8f3f0 0,transparent 30%),linear-gradient(135deg,#f8fbff,#edf3f9)} .shell{width:min(760px,calc(100% - 32px));margin:0 auto;padding:clamp(42px,10vh,96px) 0}.brand{display:flex;gap:12px;align-items:center;color:var(--navy);font-size:14px;font-weight:750;letter-spacing:.04em;text-transform:uppercase}.brand-mark{width:34px;height:34px;display:grid;place-items:center;color:white;font-weight:800;border-radius:11px;background:linear-gradient(135deg,var(--blue),#254f94)}.panel{margin-top:24px;padding:clamp(26px,5vw,46px);background:#fff;border-radius:24px;box-shadow:0 24px 70px #102a431c}h1{max-width:620px;margin:0;color:var(--navy);font-size:clamp(28px,5vw,42px);letter-spacing:-.04em;line-height:1.08}.lead{max-width:610px;margin:15px 0 30px;color:var(--muted);font-size:16px;line-height:1.7}.progress{height:8px;overflow:hidden;border-radius:99px;background:#e8eef5}.progress-bar{height:100%;border-radius:inherit;background:linear-gradient(90deg,var(--blue),var(--mint));transition:width .5s ease}.progress-copy{display:flex;justify-content:space-between;margin:10px 0 24px;color:var(--muted);font-size:13px}.checks{margin:0;padding:0;list-style:none;border-top:1px solid var(--line)}.check{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 2px;border-bottom:1px solid var(--line)}.check-title{display:flex;align-items:center;gap:10px;font-size:14px;font-weight:650}.dot{width:22px;height:22px;display:grid;place-items:center;flex:0 0 auto;border-radius:50%;font-size:12px}.dot.ok{color:#087e68;background:#d9f8ef}.dot.wait{color:#9a5b00;background:#fff0cc}.badge{padding:5px 8px;border-radius:999px;font-size:11px;font-weight:750;letter-spacing:.05em}.badge.ok{color:#087e68;background:#d9f8ef}.badge.wait{color:#9a5b00;background:#fff0cc}.action{display:flex;gap:14px;align-items:center;margin-top:28px}.button{border:0;border-radius:11px;padding:13px 18px;color:white;cursor:pointer;font:inherit;font-weight:700;background:var(--navy)}.button:disabled{cursor:wait;opacity:.65}.action-note{color:var(--muted);font-size:13px;line-height:1.45}.running,.error{display:none;margin-top:20px;padding:12px 14px;border-radius:10px;font-size:14px}.running{align-items:center;gap:10px;color:#0a5a8d;background:#e8f5ff}.error{color:#a52222;background:#ffebeb}.spinner{width:17px;height:17px;border:2px solid #8cc9ec;border-top-color:#1678b9;border-radius:50%;animation:spin .8s linear infinite}@@keyframes spin{to{transform:rotate(360deg)}}@@media(max-width:520px){.action{align-items:flex-start;flex-direction:column}.check{align-items:flex-start}.badge{margin-left:32px}}
</style>
</head>
<body>
@php
$checks = [
['Koneksi dan keamanan database', $status['pgcrypto']],
['Identitas petugas', $status['pegawai_columns']['id']],
['Informasi kontak petugas', $status['pegawai_columns']['email']],
['Verifikasi akun petugas', $status['pegawai_columns']['email_verified_at']],
['Keamanan sesi petugas', $status['pegawai_columns']['remember_token']],
['Akses masuk aplikasi', $status['users_view']],
['Riwayat verifikasi transaksi', $status['veri_time']],
['Penyimpanan sesi aplikasi', $status['sessions']],
];
$readyCount = count(array_filter(array_column($checks, 1)));
@endphp
<main class="shell"><div class="brand"><span class="brand-mark">P</span>P-Solution Parking</div><section class="panel"><h1>Menyiapkan aplikasi untuk digunakan.</h1><p class="lead">Kami sedang memeriksa kebutuhan awal aplikasi. Data dan pengaturan yang sudah ada tetap aman, lalu aplikasi akan menyesuaikan diri dengan sistem parkir di lokasi ini.</p><div class="progress"><div class="progress-bar" style="width:{{ (int) round(($readyCount / count($checks)) * 100) }}%"></div></div><div class="progress-copy"><span>Status persiapan</span><span>{{ $readyCount }}/{{ count($checks) }} bagian siap</span></div><ul class="checks">@foreach($checks as [$label, $ready])<li class="check"><span class="check-title"><span class="dot {{ $ready ? 'ok' : 'wait' }}">{{ $ready ? '✓' : '!' }}</span>{{ $label }}</span><span class="badge {{ $ready ? 'ok' : 'wait' }}">{{ $ready ? 'SIAP' : 'PERLU DISIAPKAN' }}</span></li>@endforeach</ul><div class="action"><button id="runSetup" class="button" type="button">Siapkan Aplikasi</button><span class="action-note">Proses ini tidak menghapus data transaksi atau data operasional yang sudah ada.</span></div><div id="running" class="running"><span class="spinner"></span><span id="runningText">Menyiapkan aplikasi...</span></div><div id="error" class="error"></div></section></main>
<script>
const button=document.getElementById('runSetup'),running=document.getElementById('running'),runningText=document.getElementById('runningText'),errorBox=document.getElementById('error');button.addEventListener('click',async()=>{button.disabled=true;button.textContent='Sedang menyiapkan...';running.style.display='flex';errorBox.style.display='none';const messages=['Memeriksa kesiapan sistem...','Menyesuaikan data petugas...','Menyiapkan akses aplikasi...','Menyelesaikan pengaturan awal...'];let index=0;const interval=setInterval(()=>runningText.textContent=messages[index++%messages.length],900);try{const response=await fetch('{{ route('setup.run') }}',{method:'POST',headers:{'X-CSRF-TOKEN':document.querySelector('meta[name="csrf-token"]').content,'Accept':'application/json'}});const result=await response.json();if(!response.ok||!result.success)throw new Error(result.message||'Persiapan aplikasi belum berhasil.');clearInterval(interval);runningText.textContent='Aplikasi siap. Mengalihkan ke halaman login...';setTimeout(()=>window.location.assign(result.redirect),800)}catch(error){clearInterval(interval);running.style.display='none';errorBox.textContent=error.message;errorBox.style.display='block';button.disabled=false;button.textContent='Coba Siapkan Lagi'}});
</script>
</body>
</html>
@@ -1,8 +1,7 @@
@extends('layouts.master')
@section('styles')
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css">
<style>
.card-body label {
font-size: 13px;
@@ -53,14 +52,13 @@
<div class="main-panel">
<div class="content-wrapper">
<div class="row">
<div class="col-2">
</div>
<div class="col-12 col-md-8 grid-margin justify-content-center stretch-card">
<div class="col-12 col-md-8 grid-margin stretch-card">
<div class="card shadow-sm h-100">
<div class="card">
<div class="card-body">
<h4 class="card-title">Daftar Pintu/Pos</h4>
<div class="table-responsive mb-2">
<table class="table table-bordered table-selectable" id="example" data-url="{{ route('getMember') }}">
<table class="table table-bordered table-selectable" id="example" data-url="">
<thead>
<tr>
<th class="p-3" width="3%">No.</th>
@@ -96,6 +94,7 @@
</div>
</div>
</div>
<!--</div>-->
</div>
<div class="col-2">
<!-- ADD MEMMBER MODAL -->
+63 -29
View File
@@ -162,19 +162,25 @@
let id = $(this).data('id');
let nama = $(this).data('nama');
// # base url tanpa parameter biar tidak dobel slash
let baseUrl = "{{ url('setup/master/gate-hardware') }}";
$.ajax({
url: "{{ route('hardware.show', '') }}/" + id,
url: baseUrl + "/" + id,
type: 'GET',
success: function (r) {
r = r || {};
console.log(r);
let colSpan = row.children('td').length;
// Field yang mau ditampilkan (pilih sendiri mau yang mana)
// # daftar field yang ditampilkan (key database + label)
let fields = [
['printer1_avail', 'Printer Status'],
['printer1_conn', 'Printer Connection'],
['printer1_spd', 'Printer Speed/Baudrate'],
['printer1_dummy', 'Jarak Potong Kertas'],
['video1_avail', 'Kamera Status'],
['video1_conn', 'Kamera URL/Path'],
['sound_avail', 'Sound Status'],
@@ -183,25 +189,32 @@
['bank_reader_avail', 'Reader BANK Status'],
['bank_reader_comm', 'Reader BANK Connection'],
['bank_reader_key', 'Reader BANK Key/Parameter'],
['smartcard_avail', 'Smartcard Status'],
['smartcard_speed', 'Smartcard Speed/Baudrate'],
['smartcard_port', 'Smartcard Port'],
];
let rows = '';
let cards = '';
for (let i = 0; i < fields.length; i ++) {
let f1 = fields[i];
let val = r[f1[0]] ?? '';
rows += `
<tr>
<td class="fw-semibold text-muted text-label"><strong>${f1[1]}</strong></td>
<td>
cards += `
<div class="col-12 col-sm-6 col-lg-3 mb-2">
<div class="border rounded p-2 h-100">
<div class="fw-semibold text-muted text-label mb-1"><strong>${f1[1]}</strong></div>
<div>
<span class="view-mode">${formatValue(f1[0], r[f1[0]])}</span>
<input type="text"
class="form-control form-control-sm edit-mode d-none"
data-key="${f1[0]}"
value="${r[f1[0]] ?? ''}">
</td>
</tr>
data-orig="${esc(val)}"
value="${esc(val)}">
</div>
</div>
</div>
`;
}
@@ -210,7 +223,7 @@ class="form-control form-control-sm edit-mode d-none"
<td colspan="${colSpan}">
<div class="card shadow-sm">
<div class="card-header d-flex justify-content-between align-items-center">
<strong>Hardware Configuration</strong>
<strong>Hardware Configuration - ${esc(nama)} (${esc(id)})</strong>
<div>
<button class="btn btn-sm btn-primary btn-edit">Edit</button>
<button class="btn btn-sm btn-success btn-save d-none">Save</button>
@@ -218,17 +231,9 @@ class="form-control form-control-sm edit-mode d-none"
</div>
</div>
<div class="card-body p-2">
<table class="table table-bordered table-sm align-middle mb-0">
<thead class="table-light">
<tr>
<th width="15%">Setting</th>
<th width="85%">Value</th>
</tr>
</thead>
<tbody>
${rows}
</tbody>
</table>
<div class="row">
${cards}
</div>
</div>
</div>
</td>
@@ -256,6 +261,11 @@ class="form-control form-control-sm edit-mode d-none"
// CANCEL
$(document).on('click', '.btn-cancel', function () {
// # kembalikan isian ke nilai semula
$('.edit-mode').each(function () {
$(this).val($(this).data('orig') ?? '');
});
$('.edit-mode').addClass('d-none');
$('.view-mode').removeClass('d-none');
@@ -267,6 +277,7 @@ class="form-control form-control-sm edit-mode d-none"
$(document).on('click', '.btn-save', function () {
let dataToSend = {};
let gateId = $('.edit-row').data('id');
$('.edit-mode').each(function () {
let key = $(this).data('key');
@@ -274,28 +285,48 @@ class="form-control form-control-sm edit-mode d-none"
});
console.log(dataToSend);
let gateId = $('.edit-row').data('id');
// # base url tanpa parameter biar tidak dobel slash
let baseUrl = "{{ url('setup/master/gate-hardware') }}";
$.ajax({
url: "{{ route('hardware.update', '') }}/" + gateId,
type: "PUT",
url: baseUrl + "/" + gateId,
type: "POST",
data: {
_token: "{{ csrf_token() }}",
_method: "PUT",
data: dataToSend
},
success: function () {
success: function (res) {
// # tampilkan ulang nilai yang baru disimpan
$('.edit-mode').each(function () {
let key = $(this).data('key');
let val = $(this).val();
$(this).siblings('.view-mode').text(val);
$(this).data('orig', val);
$(this).siblings('.view-mode').html(formatValue(key, val));
});
$('.btn-cancel').click();
alert('Berhasil disimpan 🔥');
alert('Berhasil disimpan');
},
error: function (xhr) {
console.error(xhr.responseText);
alert('Gagal menyimpan. Cek koneksi / hubungi admin.');
}
});
});
// # amankan teks biar tanda kutip tidak merusak HTML
function esc(value) {
if (value === null || value === undefined) return '';
return String(value)
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
function formatValue(key, value) {
// kalau field *_avail kita kasih badge
@@ -309,7 +340,10 @@ function formatValue(key, value) {
}
// default
return value ?? '-';
if (value === null || value === undefined || value === '') {
return '-';
}
return esc(value);
}
});
+603
View File
@@ -0,0 +1,603 @@
@extends('layouts.master')
@section('title', 'DHCP Reservations')
@section('content')
<div class="container-fluid py-4">
{{-- Header --}}
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h4 class="mb-1">DHCP Reservations</h4>
<div class="text-muted small">Manage static DHCP leases by MAC address</div>
</div>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#reservationModal" onclick="openCreateModal()"><i class="bi bi-plus-lg me-1"></i> Add Reservation</button>
</div>
{{-- DHCP Server Card --}}
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<div class="row align-items-center">
{{-- Server --}}
<div class="col-md-5">
<div class="d-flex align-items-center">
<div class="rounded-circle bg-success bg-opacity-10 d-flex align-items-center justify-content-center me-3" style="width:48px;height:48px;">
<i class="bi bi-hdd-network text-success fs-4"></i>
</div>
<div>
<div class="fw-semibold" id="dhcpServerHostname">DHCP LAB</div>
<div class="text-muted small"> <span id="dhcpServerIp">-</span>
<span class="mx-1">•</span>
<span id="dhcpServerInterface">-</span></div>
</div>
</div>
</div>
{{-- Status --}}
<div class="col-md-3 mt-3 mt-md-0">
<div class="small text-muted">Status</div>
<div class="mt-1">
@if($dhcpStatus['running'])
<span id="dhcpStatusBadge" class="badge rounded-pill bg-success bg-opacity-10 text-success">
<span
class="d-inline-block rounded-circle bg-success me-1"
style="width:7px;height:7px;"
></span>
Running
</span>
@else
<span id="dhcpStatusBadge" class="badge rounded-pill bg-danger bg-opacity-10 text-danger">
<span
class="d-inline-block rounded-circle bg-danger me-1"
style="width:7px;height:7px;"
></span>
Stopped
</span>
@endif
</div>
</div>
{{-- Reservations --}}
<div class="col-md-2 mt-3 mt-md-0">
<div class="small text-muted">
Reservations
</div>
<div class="fw-semibold mt-1" id="activeClients">
{{ $dhcpStatus['active_clients'] }}
<span class="text-muted fw-normal small">
active
</span>
</div>
</div>
{{-- Last Apply --}}
<div class="col-md-2 mt-3 mt-md-0">
<div class="small text-muted">
Last Applied
</div>
<div
class="fw-semibold mt-1"
id="lastApplied"
>
-
</div>
</div>
</div>
</div>
</div>
{{-- Filters --}}
<div class="card border-0 shadow-sm mb-3">
<div class="card-body py-3">
<div class="row g-2">
<div class="col-md-5">
<div class="input-group">
<span class="input-group-text bg-white">
<i class="bi bi-search"></i>
</span>
<input
type="text"
class="form-control form-control-sm border-start-0"
id="searchReservation"
placeholder="Search hostname, MAC or IP..."
>
</div>
</div>
<div class="col-md-3">
<select
class="form-select"
id="statusFilter"
>
<option value="">All Status</option>
<option value="active">Active</option>
<option value="disabled">Disabled</option>
</select>
</div>
</div>
</div>
</div>
{{-- Reservation Table --}}
<div class="card border-0 shadow-sm">
<div class="card-body p-0">
<div class="table-responsive">
<table
class="table table-hover align-middle mb-0"
id="reservationTable">
<thead class="table-light">
<tr>
<th class="px-4">Hostname</th>
<th>MAC Address</th>
<th>IP Address</th>
<th>Status</th>
<th class="text-end px-4"></th>
</tr>
</thead>
<tbody>
@forelse($reservations as $reservation)
<tr>
<td class="px-4">
<div class="fw-semibold">
{{ $reservation->hostname }}
</div>
@if(!empty($reservation->description))
<div class="text-muted small">
{{ $reservation->description }}
</div>
@endif
</td>
<td>
<code>{{ $reservation->mac_address }}</code>
</td>
<td>
<span class="fw-semibold">
{{ $reservation->ip_address }}
</span>
</td>
<td>
@if($reservation->enabled)
<span class="badge rounded-pill bg-success bg-opacity-10 text-success">
Active
</span>
@else
<span class="badge rounded-pill bg-secondary bg-opacity-10 text-secondary">
Disabled
</span>
@endif
</td>
<td class="text-end px-4">
<div class="btn-group">
<button
type="button"
class="btn btn-sm btn-light"
onclick="editReservation(
@js($reservation->id),
@js($reservation->hostname),
@js($reservation->mac_address),
@js($reservation->ip_address),
@js((bool) $reservation->enabled)
)"
>
<i class="bi bi-pencil"></i>
</button>
<button
type="button"
class="btn btn-sm btn-light text-danger"
onclick="deleteReservation(
@js($reservation->id),
@js($reservation->hostname)
)"
>
<i class="bi bi-trash"></i>
</button>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="text-center py-5 text-muted">
No DHCP reservations found.
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
{{-- Footer --}}
<div class="card-footer bg-white border-0">
<div class="d-flex justify-content-between align-items-center">
<div class="text-muted small">
Showing {{ $reservations->count() }} reservations
</div>
<div>
@if(method_exists($reservations, 'previousPageUrl') && $reservations->previousPageUrl())
<a href="{{ $reservations->previousPageUrl() }}"
class="btn btn-sm btn-outline-secondary">
Previous
</a>
@else
<button class="btn btn-sm btn-outline-secondary" disabled>
Previous
</button>
@endif
@if(method_exists($reservations, 'nextPageUrl') && $reservations->nextPageUrl())
<a href="{{ $reservations->nextPageUrl() }}"
class="btn btn-sm btn-outline-secondary">
Next
</a>
@else
<button class="btn btn-sm btn-outline-secondary" disabled>
Next
</button>
@endif
</div>
</div>
</div>
</div>
{{-- Apply Section --}}
<div class="card border-0 shadow-sm mt-4">
<div class="card-body">
<div class="row align-items-center">
<div class="col">
<div class="fw-semibold">
DHCP Configuration
</div>
<div class="text-muted small mt-1">
Changes are stored in the database
until you apply the configuration.
</div>
</div>
<div class="col-auto">
<button
class="btn btn-outline-secondary me-2"
onclick="testDhcp()"
>
<i class="bi bi-shield-check me-1"></i>
Test Configuration
</button>
<button
class="btn btn-primary"
onclick="applyDhcp()"
>
<i class="bi bi-arrow-repeat me-1"></i>
Apply Changes
</button>
</div>
</div>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card-header bg-white border-0
d-flex justify-content-between
align-items-center">
<div>
<div class="fw-semibold">
Recent DHCP Events
</div>
<div class="text-muted small">
Latest activity from DHCP server
</div>
</div>
<button
type="button"
class="btn btn-sm btn-light"
onclick="loadDhcpLogs()"
>
<i class="bi bi-arrow-clockwise"></i>
Refresh
</button>
</div>
<div class="card-body p-0">
<div
id="dhcpLogs"
class="font-monospace small"
style="
max-height: 320px;
overflow-y: auto;
"
>
<div class="text-muted p-4">
Loading DHCP events...
</div>
</div>
</div>
</div>
</div>
{{-- Add / Edit Modal --}}
<div
class="modal fade"
id="reservationModal"
tabindex="-1"
>
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow">
<div class="modal-header">
<div>
<h5
class="modal-title mb-1"
id="reservationModalTitle"
>
Add DHCP Reservation
</h5>
<div class="text-muted small">
Assign a fixed IP address to a DHCP client
</div>
</div>
<button
type="button"
class="btn-close"
data-bs-dismiss="modal"
></button>
</div>
<form id="reservationForm">
<div class="modal-body">
<input
type="hidden"
id="reservationId"
value=""
>
{{-- Hostname --}}
<div class="mb-3">
<label class="form-label fw-semibold">
Hostname
</label>
<input
type="text"
class="form-control"
id="hostname"
placeholder="e.g. kasir-01"
required
>
<div class="form-text">
Only letters, numbers, dash and underscore.
</div>
</div>
{{-- MAC --}}
<div class="mb-3">
<label class="form-label fw-semibold">
MAC Address
</label>
<input
type="text"
class="form-control font-monospace"
id="macAddress"
placeholder="AA:BB:CC:DD:EE:FF"
maxlength="17"
required
>
<div
class="invalid-feedback"
id="macError"
>
Invalid MAC address.
</div>
</div>
{{-- IP --}}
<div class="mb-3">
<label class="form-label fw-semibold">
IP Address
</label>
<input
type="text"
class="form-control font-monospace"
id="ipAddress"
placeholder="192.168.10.50"
required
>
<div
class="invalid-feedback"
id="ipError"
>
Invalid IP address.
</div>
</div>
{{-- Enabled --}}
<div
class="form-check form-switch"
>
<input
class="form-check-input"
type="checkbox"
id="enabled"
checked
>
<label
class="form-check-label"
for="enabled"
>
Enable reservation
</label>
</div>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-light"
data-bs-dismiss="modal"
>
Cancel
</button>
<button
type="submit"
class="btn btn-primary"
>
<i class="bi bi-check-lg me-1"></i>
Save Reservation
</button>
</div>
</form>
</div>
</div>
</div>
{{-- Apply Confirmation --}}
<div
class="modal fade"
id="applyModal"
tabindex="-1"
>
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow">
<div class="modal-body text-center p-4">
<div
class="rounded-circle bg-warning bg-opacity-10
d-flex align-items-center justify-content-center
mx-auto mb-3"
style="width:64px;height:64px;"
>
<i class="bi bi-exclamation-triangle text-warning fs-3"></i>
</div>
<h5>
Apply DHCP Configuration?
</h5>
<p class="text-muted mb-4">
The DHCP configuration will be validated
before the DHCP service is restarted.
</p>
<button
class="btn btn-light me-2"
data-bs-dismiss="modal"
>
Cancel
</button>
<button
class="btn btn-primary"
onclick="confirmApply()"
>
Apply Configuration
</button>
</div>
</div>
</div>
</div>
@endsection
@section('javascript')
<script>
window.dhcpConfig = {
reservationStoreUrl: @json(route('dhcp.reservations.store')),
reservationBaseUrl: @json(url('/dhcp/reservations')),
applyUrl: @json(route('dhcp.apply')),
statusUrl: @json(route('dhcp.status')),
logsUrl: @json(route('dhcp.logs')),
};
</script>
<script src="{{ asset('js/setup/dhcp.js') }}"></script>
@endsection
+53 -14
View File
@@ -56,11 +56,11 @@
<div class="card-body">
<h4 class="card-title">Master Kendaraan dan Tarif Parkir</h4>
<div class="table-responsive mb-2">
<table class="table table-bordered table-selectable" id="example" data-url="{{ route('getMember') }}">
<table class="table table-bordered table-selectable" id="vehicle-table">
<thead>
<tr>
<th class="p-3" width="5%">No.</th>
<th style="text-align: center;">Nama Pintu - <span class="fw-bold">(Shortcut)</span></th>
<th style="text-align: center;">Jenis Kendaraan - <span class="fw-bold">(Shortcut)</span></th>
{{-- <th style="text-align: center;">Mode Pintu</th> --}}
{{-- <th style="text-align: center;">Grace Periode</th> --}}
{{-- <th style="text-align: center;">Manless?</th> --}}
@@ -73,9 +73,7 @@
{{-- {{ dd($kendaraan) }} --}}
<td class="text-center">{{ $loop->iteration }}</td>
<label for="" type="hidden" value="{{ $kendaraan->id }}" ></label>
{{-- <td class="text-center" width="7%">{{ $pintu->id }}</td> --}}
<td>{{ $kendaraan->nama }} <strong> - ({{ $kendaraan->short_cut }})</strong></td>
<td data-vehicle-id="{{ $kendaraan->id }}" data-vehicle-name="{{ $kendaraan->nama }}" data-vehicle-shortcut="{{ $kendaraan->short_cut }}">{{ $kendaraan->nama }} <strong> - ({{ $kendaraan->short_cut }})</strong></td>
{{-- <td width="10%">
</td> --}}
{{-- <td class="text-center" width="7%"><span class="d-inline-block rounded-circle
@@ -88,7 +86,7 @@
</span>
</td> --}}
<td class="text-center" width="12%">
<a class="btn btn-warning btn-sm vehicle-price" id="vehicle-price" data-bs-toggle="modal" data-id="{{ $kendaraan->id}}" data-nama="{{ $kendaraan->nama}}" data-bs-target=".bd-price-modal-lg">Tarif</a>
<button class="btn btn-warning btn-sm vehicle-price" type="button" data-bs-toggle="modal" data-id="{{ $kendaraan->id}}" data-nama="{{ $kendaraan->nama}}" data-bs-target=".bd-price-modal-lg">Tarif</button>
{{-- <button class="btn btn-primary btn-sm hardware-button" data-id="{{ $pintu->id }}" data-nama="{{ $pintu->nama }}">Hardware</button> --}}
</td>
</tr>
@@ -96,14 +94,12 @@
</tbody>
</table>
</div>
<div class="d-flex justify-content-center align-items-center mt-2">
<div>
<button class="btn btn-primary btn-sm add-button" data-bs-toggle="modal" data-bs-target=".bd-example-modal-lg">Tambah</button>
{{-- </div>
<div id="rowActions" class="d-none"> --}}
<button class="btn btn-warning btn-sm edit-button" id="btnEdit" data-bs-toggle="modal" data-bs-target=".bd-ubah-modal-lg">Tambah</button>
<button class="btn btn-danger btn-sm" id="btnDelete">Hapus</button>
<button class="btn btn-info btn-sm" id="btnDetail">Detail</button>
<div class="d-flex justify-content-between align-items-center mt-3">
<button class="btn btn-primary btn-sm" id="btnAddVehicle" type="button"><i class="fa fa-plus me-1"></i>Tambah</button>
<div class="d-flex gap-2">
<button class="btn btn-info btn-sm" id="btnDetail" type="button" disabled><i class="fa fa-circle-info me-1"></i>Detail</button>
<button class="btn btn-warning btn-sm" id="btnEditVehicle" type="button" disabled><i class="fa fa-pen me-1"></i>Ubah</button>
<button class="btn btn-danger btn-sm" id="btnDeleteVehicle" type="button" disabled><i class="fa fa-trash me-1"></i>Hapus</button>
</div>
</div>
<!-- ADD MEMMBER MODAL -->
@@ -122,6 +118,12 @@
</div>
</div>
</div>
<div class="modal fade" id="vehicle-detail-modal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered"><div class="modal-content">
<div class="modal-header"><h5 class="modal-title">Detail Jenis Kendaraan</h5><button class="btn-close" data-bs-dismiss="modal"></button></div>
<div class="modal-body" id="vehicle-detail-body"></div>
</div></div>
</div>
</div>
@endsection
@@ -141,5 +143,42 @@
<script src="{{asset ('js/member/index.js') }}"></script>
{{-- <script src="{{asset ('js/setup/vehicle-price.js') }}"></script> --}}
<script src="{{asset ('js/setup/vehicle.js') }}"></script>
<script>
$(function () {
let selected = null;
const csrf = $('meta[name="csrf-token"]').attr('content');
const routes = {
store: '{{ route('vehicle.store', [], false) }}',
show: '{{ route('vehicle.show', ':id', false) }}',
update: '{{ route('vehicle.update', ':id', false) }}',
destroy: '{{ route('vehicle.destroy', ':id', false) }}'
};
function setSelected(row) {
$('#vehicle-table tbody tr').removeClass('table-active');
selected = row ? $(row).find('[data-vehicle-id]').data() : null;
if (row) $(row).addClass('table-active');
$('#btnDetail, #btnEditVehicle, #btnDeleteVehicle').prop('disabled', !selected);
}
function formRow(mode, vehicle) {
const isEdit = mode === 'edit';
return `<tr class="vehicle-form-row"><td colspan="3"><form class="vehicle-inline-form" data-mode="${mode}" data-id="${vehicle?.vehicleId || ''}"><div class="row g-2 align-items-end"><div class="col-md-2"><label class="form-label mb-1">ID</label><input class="form-control" name="id" maxlength="16" value="${vehicle?.vehicleId || ''}" ${isEdit ? 'readonly' : ''} required></div><div class="col-md-4"><label class="form-label mb-1">Nama Kendaraan</label><input class="form-control" name="nama" maxlength="100" value="${vehicle?.vehicleName || ''}" required></div><div class="col-md-2"><label class="form-label mb-1">Shortcut</label><input class="form-control text-uppercase" name="short_cut" maxlength="16" value="${vehicle?.vehicleShortcut || ''}" required></div>${isEdit ? '' : '<div class="col-md-2"><label class="form-label mb-1">Tarif Awal</label><input class="form-control" type="number" name="tarif" min="0" step="0.01" value="0" required></div>'}<div class="col-md-2 d-flex gap-2"><button class="btn btn-success" type="submit">Simpan</button><button class="btn btn-light border cancel-vehicle-form" type="button">Batal</button></div></div></form></td></tr>`;
}
$('#vehicle-table tbody').on('click', 'tr:not(.vehicle-form-row)', function () { setSelected(this); });
$('#btnAddVehicle').on('click', function () { $('.vehicle-form-row').remove(); $('#vehicle-table tbody').prepend(formRow('create')); setSelected(null); });
$('#btnEditVehicle').on('click', function () { if (!selected) return; $('.vehicle-form-row').remove(); $('#vehicle-table tbody tr').filter(function(){ return $(this).find('[data-vehicle-id]').data('vehicle-id') === selected.vehicleId; }).after(formRow('edit', selected)); });
$(document).on('click', '.cancel-vehicle-form', function () { $(this).closest('tr').remove(); });
$(document).on('submit', '.vehicle-inline-form', function (event) {
event.preventDefault(); const form = $(this); const id = form.data('id'); const edit = form.data('mode') === 'edit';
$.ajax({ url: edit ? routes.update.replace(':id', encodeURIComponent(id)) : routes.store, type: edit ? 'PUT' : 'POST', data: form.serialize(), headers: {'X-CSRF-TOKEN': csrf}, success: () => window.location.reload(), error: xhr => alert(xhr.responseJSON?.message || Object.values(xhr.responseJSON?.errors || {}).flat().join('\n') || 'Gagal menyimpan kendaraan.') });
});
$('#btnDeleteVehicle').on('click', function () { if (!selected) return; Swal.fire({title:'Hapus kendaraan?', text:`${selected.vehicleName} beserta tarif terkait akan dihapus.`, icon:'warning', showCancelButton:true, confirmButtonText:'Ya, hapus', cancelButtonText:'Batal', confirmButtonColor:'#dc3545'}).then(result => { if (!result.isConfirmed) return; $.ajax({url: routes.destroy.replace(':id', encodeURIComponent(selected.vehicleId)), type:'DELETE', headers:{'X-CSRF-TOKEN':csrf}, success:() => window.location.reload(), error:xhr => Swal.fire({icon:'error', title:'Gagal menghapus', text:xhr.responseJSON?.message || 'Kendaraan tidak dapat dihapus.'})}); }); });
$('#btnDetail').on('click', function () { if (!selected) return; $('#vehicle-detail-body').html('<div class="text-center py-3">Memuat detail...</div>'); new bootstrap.Modal(document.getElementById('vehicle-detail-modal')).show(); $.get(routes.show.replace(':id', encodeURIComponent(selected.vehicleId)), function(r) { const t = r.tarif; $('#vehicle-detail-body').html(`<dl class="row mb-0"><dt class="col-5">ID Kendaraan</dt><dd class="col-7">${r.data.id}</dd><dt class="col-5">Nama</dt><dd class="col-7">${r.data.nama}</dd><dt class="col-5">Shortcut</dt><dd class="col-7">${r.data.short_cut}</dd><dt class="col-5">Tarif Awal</dt><dd class="col-7">${t ? Number(t.tarif || 0).toLocaleString('id-ID') : '-'}</dd><dt class="col-5">Tarif Interval</dt><dd class="col-7">${t ? Number(t.tarif_interval || 0).toLocaleString('id-ID') : '-'}</dd><dt class="col-5">Waktu Gratis</dt><dd class="col-7">${t?.waktu_gratis ?? '-' } menit</dd></dl>`); }); });
});
</script>
@endsection
@@ -22,7 +22,7 @@
<form class="tambah-member" id="show-tarif" action="">
@csrf
<div class="tab-content">
<div class="tab-pane fade show active" id="tarif1" role="tabpanel">
<div class="tab-pane fade show active" id="tarif" role="tabpanel">
<div class="pt-4">
<div class="row">
<div class="col-xl-12">
@@ -60,7 +60,7 @@
<option value="0">Pintu Keluar</option>
</select>
<label class="me-2 mb-0">Dengan nilai asuransi : </label>
<input type="number" class="form-control no-spinner" name="asuransi_incl" name="asuransi_incl" style="width:100px" value="0">
<input type="number" class="form-control no-spinner" id="asuransi_incl" name="asuransi_incl" style="width:100px" value="0">
</div>
<hr>
<h4 class="fw-bold">Jam Berikutnya</h4>
@@ -85,13 +85,13 @@
<label class="me-2 mb-0">Tarif Maksimum adalah </label>
<input type="number" class="form-control no-spinner" id="maksimum" name="maksimum" style="width:100px" value="1000">
<label class="me-2 ms-4 mb-0">Per</label>
<input type="number" class="form-control no-spinner-time" id="maks_cond" name="maks_cond" style="width:65px" value="24">
<select class="form-select me-2 ms-4 no-spinner" name="maksimum-cond" id="maksimum-cond" style="width:100px" >
<option value="0">Jam</option>
<option value="1">Hari</option>
<input type="number" class="form-control no-spinner-time" id="maksimum_hour" name="maksimum_hour" style="width:65px" value="24">
<select class="form-select me-2 ms-4 no-spinner" name="maksimum_mode" id="maksimum_mode" style="width:100px" >
<option value="hour">Jam</option>
<option value="day">Hari</option>
</select>
<label class="me-2 ms-4 mb-0 maks_cond_day">Minimum Menit : </label>
<input type="number" class="form-control no-spinner-time" id="maks_cond_day" name="maks_cond_day" style="width:75px" value="24">
<input type="number" class="form-control no-spinner-time" id="maksimum_day_minute" name="maksimum_day_minute" style="width:75px" value="24">
</div>
<hr>
<h4 class="fw-bold">Denda Tiket</h4>
@@ -104,10 +104,10 @@
</div>
<hr>
<div class="d-flex align-items-center">
<input class="form-check-input" type="checkbox">Denda Hanya Berlaku Bagi Kendaaran Casual/Umum </input>
<input class="form-check-input me-2" type="checkbox" id="denda_only_casual" name="denda_only_casual" value="1"><label for="denda_only_casual">Denda hanya berlaku bagi kendaraan casual/umum</label>
</div>
<div class="d-flex align-items-center mb-2">
<input class="form-check-input" type="checkbox">Denda Tidak Memperhitungkan Lama Parkir</input>
<input class="form-check-input me-2" type="checkbox" id="denda_only" name="denda_only" value="1"><label for="denda_only">Denda tidak memperhitungkan lama parkir</label>
</div>
</div>
{{-- <div class="col-xl-6">
@@ -241,7 +241,7 @@
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary simpan" data-url="{{ route('vehicle.update', ':id') }}">Save changes</button>
<button type="button" class="btn btn-primary simpan" data-url="{{ route('vehiclePrice.update', ':id', false) }}">Save changes</button>
<button type="button" class="btn btn-danger light" data-bs-dismiss="modal">Close</button>
</div>
</div>
@@ -0,0 +1,27 @@
<div
class="modal fade bd-details-modal-lg"
id="modal-details"
tabindex="-1"
role="dialog"
data-bs-backdrop="static"
data-keyboard="false"
>
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h4 class="card-title">Preview Member</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button type="button" class="btn btn-danger light" data-bs-dismiss="modal">
Close
</button>
<button type="button" class="btn btn-outline-inverse-info btn-sm btn-icon-text">
Print
<i class="mdi mdi-printer btn-icon-append"></i>
</button>
</div>
</div>
</div>
</div>
+54 -37
View File
@@ -1,8 +1,8 @@
<div class="modal fade bd-extend-modal-lg" id="extend-member" tabindex="-1" role="dialog" data-bs-backdrop="static" data-keyboard="false">
<div class="modal fade bd-extend-modal-lg" id="modal-extend" tabindex="-1" role="dialog" data-bs-backdrop="static" data-keyboard="false">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h4 class="card-title">Ubah Data Stiker/Member</h4>
<h4 class="card-title">Perpanjang Member</h4>
<button type="button" class="btn-close" data-bs-dismiss="modal">
</button>
</div>
@@ -10,106 +10,123 @@
<div class="custom-tab-1">
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#home1"><i class="la la-user me-2"></i>Data Pengunjung</a>
<a class="nav-link active" data-bs-toggle="tab" href="#ext-data"><i class="la la-user me-2"></i>Data Pengunjung</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#profile1"><i class="la la-home me-2"></i> Data Kendaraan</a>
<a class="nav-link" data-bs-toggle="tab" href="#ext-vehicle"><i class="la la-home me-2"></i> Data Kendaraan</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#contact1"><i class="la la-phone me-2"></i>Data Akses</a>
<a class="nav-link" data-bs-toggle="tab" href="#ext-card"><i class="la la-phone me-2"></i>Data Akses</a>
</li>
</ul>
<form class="ubah-member" id="edit-member" methode="POST">
@csrf
@method('PUT')
<form class="extend-member" id="extend-member" action="{{ route('member.store')}}">
<div class="tab-content">
<div class="tab-pane fade show active" id="home1" role="tabpanel">
<div class="tab-pane fade show active" id="ext-data" role="tabpanel">
<div class="pt-4">
<div class="row">
<div class="col-xl-6">
<div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label">No. Transaksi</label>
<div class="col-lg-8">
<input type="text" class="form-control form-control-sm notransnya" name="notrans" readonly>
<input type="hidden" class="form-control"readonly>
</div>
</div>
<div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label">Jenis Transaksi</label>
<div class="col-lg-8">
<input type="text" class="form-control" value="PERPANJANG" readonly>
<input type="text" class="form-control form-control-sm" value="PERPANJANG" readonly>
<input type="hidden" class="form-control" name="jenis_transaksi" value="1" readonly>
</div>
</div>
<div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label required">Nama</label>
<div class="col-lg-8">
<input type="text" class="form-control" name="nama" placeholder="Masuk kan nama...." required>
<input type="text" class="form-control form-control-sm namanya" name="nama" value="" required>
</div>
</div>
<div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label">Alamat</label>
<div class="col-lg-8">
<textarea type="text" class="form-control" rows="3" name="address" placeholder="JL. Hati hati raya..."></textarea>
<textarea type="text" class="form-control form-control-sm address" rows="3" name="address" value="" ></textarea>
</div>
</div>
<div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label">Telepon</label>
<div class="col-lg-8">
<input type="number" class="form-control" phone="phone" placeholder="+62 81/081...">
<input type="number" class="form-control form-control-sm" phone="phone" value="{{ old('phone')}}">
</div>
</div>
<div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label required">Periode :</label>
<div class="col-lg-8">
<input class="form-control input-daterange-timepicker" type="text" name="daterange" value="">
<input type="text" class="form-control form-control-sm input-daterange-timepicker-ext" id="periode-ext" name="periode" readonly>
</div>
</div>
</div>
<div class="col-xl-6">
<div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label">Jenis</label>
<label class="col-lg-3 col-form-label form-label">Jenis Langganan</label>
<div class="col-lg-6">
<select class="select-data form-control" name="member" id="member">
<select class="form-select" name="member" id="ext-jenisLang">
<option selected value="">Pilih Jenis</option>
@foreach ($memberSelect as $member)
<option value="{{ $member->id }}">{{ $member->nama }}</option>
@endforeach
</select>
<input type="hidden" name="member" id="ext-jenisLang-value">
</div>
</div>
<div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label">Dept</label>
<label class="col-lg-3 col-form-label form-label">Jenis Member</label>
<div class="col-lg-6">
<select class="select-data form-control" name="depart" id="department">
<select class="form-select" name="depart" id="ext-department">
<option selected value="">Pilih Departemen</option>
@foreach ($departmentSelect as $department)
<option value="{{ $department->kode }}">{{ $department->nama }}</option>
@endforeach
</select>
<input type="hidden" name="depart" id="ext-department-value">
</div>
</div>
<div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label">Note :</label>
<div class="col-lg-6">
<input type="text" class="form-control" name="note" placeholder="Catatan...">
<input type="text" class="form-control form-control-sm" name="note" placeholder="Catatan...">
</div>
</div>
<div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label">Harga (Rp.):</label>
<div class="col-lg-6">
<input type="text" class="form-control" name="harga" placeholder="10.000">
<input type="text" class="form-control form-control-sm harga" name="harga">
</div>
</div>
{{-- <div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label required">Best Skill</label>
<div class="col-lg-6">
<select class="default-select wide form-control" id="validationCustom05">
<option data-display="Select">Please select</option>
<option value="html">HTML</option>
<option value="css">CSS</option>
</select>
</div>
</div> --}}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane fade" id="profile1">
<div class="tab-pane fade" id="ext-vehicle">
<div class="pt-4">
<h4>Tambahkan Kendaraan</h4>
<div class="row form-material">
<div class="col-xl-3 col-md-6 mb-3">
<label class="form-label">Nomer Polisi</label>
<input type="text" class="form-control nopol-add" onkeyup="this.value = this.value.toUpperCase();" placeholder="No. Polisi">
<input type="text" class="form-control form-control-sm ext-nopol-add" onkeyup="this.value = this.value.toUpperCase();" placeholder="No. Polisi">
<div class="invalid-feedback">Nomer Polisi sudah ada</div>
</div>
<div class="col-xl-3 col-md-6 mb-3">
<label class="form-label">Jenis Kendaraan :</label>
<select class="select form-control" id="jenisVehicle">
<select class="form-select" id="jenisVehicleExt">
<option selected value="">Jenis Kendaraan</option>
@foreach ($vehicleSelect as $kendaraan)
<option value="{{ $kendaraan->id }}">{{ $kendaraan->nama }}</option>
@@ -118,49 +135,49 @@
</div>
<div class="col-xl-3 col-md-6 mb-3">
<label class="form-label">Jenis Member :</label>
<select class="select-data form-control member" id="jenis-member">
<select class="form-select member" id="jenisMemberExt">
</select>
</div>
<div class="col-xl-3 col-md-6 mb-3">
<label class="form-label">Merk : </label>
<input type="text" class="form-control merk" onkeyup="this.value = this.value.toUpperCase();" placeholder="Honda...">
<input type="text" class="form-control form-control-sm merkExt" onkeyup="this.value = this.value.toUpperCase();" placeholder="Honda...">
</div>
<div class="col-xl-3 col-md-6 mb-3">
<label class="form-label"></label>
<button type="button" id="nopol-add" class="btn btn-primary btn-xs">Tambah</button>
<button type="button" id="ext-nopol-add" class="btn btn-primary btn-xs">Tambah</button>
</div>
<hr>
<table class="table-bordered table-striped">
<table class="">
<thead>
<tr>
<th class="col-2 text-center p-2">Nomer Polisi</th>
<th class="col-2 text-center">Jenis Kendaraan</th>
<th class="col-2 text-center">Merk Kendaraan</th>
<th class="col-2 text-center">Type Member</th>
<th class="col-2 text-center">Merk Kendaraan</th>
<th class="col-2 text-center">Aksi</th>
</tr>
</thead>
<tbody id="list-nopol">
<tbody id="list-nopol-ext">
</tbody>
</table>
</div>
</div>
</div>
<div class="tab-pane fade" id="contact1">
<div class="tab-pane fade" id="ext-card">
<div class="pt-4">
<h4>Tambahkan Kartu</h4>
<div class="row form-material">
<div class="col-xl-6 col-md-6 mb-3">
<label class="form-label">Nomer Kartu</label>
<input type="text" class="form-control kartu" placeholder="01234567...">
<input type="text" class="form-control form-control-sm kartu" placeholder="01234567...">
</div>
<div class="col-xl-6 col-md-6 mb-3">
<label class="form-label">Nomer Urut</label>
<input type="text" class="form-control nourut" placeholder="01234567...">
<input type="text" class="form-control form-control-sm nourut" placeholder="01234567...">
</div>
<hr>
{{-- <div class="table-responsive"> --}}
<table class="table-bordered">
<table class="table-basic">
<thead>
<tr>
<th class="col-5 text-center p-2" >Nomer Kartu</th>
@@ -168,7 +185,7 @@
<th class="col-2 text-center">Aksi</th>
</tr>
</thead>
<tbody id="list-card">
<tbody id="list-card-ext">
</tbody>
</table>
{{-- </div> --}}
@@ -181,7 +198,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger light" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary simpan">Simpan Perubahan</button>
<button type="button" class="btn btn-primary simpan-ext">Save changes</button>
</div>
</div>
</div>
+30 -300
View File
@@ -51,14 +51,14 @@
<div class="col-md-2 grid-margin stretch-card">
<div class="card shadow-sm h-100">
<div class="card-body">
<form id="search-form">
<h5 class="card-title mb-4">Metode Pencarian Transaksi</h5>
<!-- DASAR PENCARIAN -->
<div class="mb-3">
<label class="form-label fw-semibold">Dasar Pencarian</label>
<select class="form-select mb-2" id="search-by">
<select class="form-select cari-data mb-2" name="search-by" id="search-by">
<option selected value="">Pencarian Data... (Semua)</option>
<option value="nama">Nama</option>
<option value="alamat">Alamat</option>
@@ -67,7 +67,7 @@
<option value="notrans">No. Transaksi</option>
</select>
<input type="text" class="form-control" id="search-value" placeholder="Masukkan kata kunci...">
<input type="text" class="form-control form-control-sm" name="search_value" id="search-value" placeholder="Masukkan kata kunci...">
</div>
<hr class="my-3">
@@ -75,7 +75,7 @@
<!-- JENIS LANGGANAN -->
<div class="mb-3">
<label class="form-label fw-semibold">Jenis Langganan</label>
<select class="form-select" id="member-type">
<select class="form-select" name="jenismember" id="member-type">
<option value="">Semua</option>
@foreach ($memberSelect as $jenisLangganan)
<option value="{{ $jenisLangganan->id }}">
@@ -88,7 +88,7 @@
<!-- UNIT KERJA -->
<div class="mb-3">
<label class="form-label fw-semibold">Unit Kerja</label>
<select class="form-select" id="department">
<select class="form-select" name="depart" id="department">
<option value="">Semua</option>
@foreach ($departmentSelect as $jeniStiker)
<option value="{{ $jeniStiker->kode }}">
@@ -101,7 +101,7 @@
<!-- KENDARAAN -->
<div class="mb-3">
<label class="form-label fw-semibold">Kendaraan</label>
<select class="form-select" id="vehicle">
<select class="form-select" name="vehicle" id="vehicle">
<option value="">Semua</option>
@foreach ($vehicleSelect as $vehicle)
<option value="{{ $vehicle->id }}">
@@ -111,6 +111,14 @@
</select>
</div>
<div class="mb-3 d-flex align-items-center justify-content-between">
<p class="mb-0">Member Aktif</p>
<label class="toggle-switch mb-0">
<input type="checkbox" name="isActive" id="isActive" checked>
<span class="toggle-slider round"></span>
</label>
</div>
<!-- KATEGORI -->
{{-- <div class="mb-4">
<label class="form-label fw-semibold">Kategori</label>
@@ -123,10 +131,10 @@ class="form-control"
<!-- BUTTON -->
<div class="d-grid">
<button type="button" id="search-button" class="btn btn-primary">
<i class="fa fa-search me-2"></i> Cari Data
<i class="fa fa-search me-2"></i> Cari Data...
</button>
</div>
</form>
</div>
</div>
</div>
@@ -139,25 +147,25 @@ class="form-control"
<button class="btn btn-primary btn-sm add-button" data-bs-toggle="modal" data-bs-target=".bd-example-modal-lg">Tambah</button>
</div>
<div id="rowActions" class="d-none">
<button class="btn btn-success btn-sm extend-button" id="btnExtend" data-bs-toggle="modal" data-bs-target=".bd-extend-modal-lg">Perpanjang</button>
<button class="btn btn-success btn-sm extend-button" id="btnExtend" data-bs-toggle="modal" data-bs-target="#modal-extend" data-id="${element.notrans}" data-url="{{ route('getStikerMember.show', ':id') }}" >Perpanjang</button>
<button class="btn btn-warning btn-sm edit-button" id="btnEdit" data-bs-toggle="modal" data-bs-target=".bd-ubah-modal-lg">Ubah</button>
<button class="btn btn-info btn-sm detail-button" id="btnDetail" data-bs-toggle="modal" data-bs-target="#modal-details">Detail</button>
<button class="btn btn-danger btn-sm" id="btnDelete">Hapus</button>
<button class="btn btn-info btn-sm" id="btnDetail">Detail</button>
</div>
</div>
<div class="table-responsive pt-3">
<table class="table table-bordered table-selectable" id="example" data-url="{{ route('getMember') }}">
<table class="table table-basic table-selectable" id="example" data-url="{{ route('getMember') }}">
<thead>
<tr>
<th class="p-3" style="text-align: center;">No.</th>
<th style="text-align: center;">No. Transaksi</th>
<th style="text-align: center;">Nama</th>
<th style="text-align: center;">Alamat</th>
<th class="p-3" style="text-align: center;" width="3%">No.</th>
<th style="text-align: center;" width="17%">No. Transaksi</th>
<th style="text-align: center;" width="8%">Nama</th>
<th style="text-align: center;" width="10%">Alamat</th>
<th style="text-align: center;">Telepone</th>
<th style="text-align: center;">Jenis Langganan</th>
<th style="text-align: center;">Departement</th>
<th style="text-align: center;">Periode</th>
<th style="text-align: center;">Details Kendaraan</th>
<th style="text-align: center;" width="11%">Periode</th>
<th style="text-align: center;" width="10%">Details Kendaraan</th>
<th style="text-align: center;">No. Kartu</th>
{{-- <th width="11%" style="text-align: center;">Action</th> --}}
</tr>
@@ -170,6 +178,7 @@ class="form-control"
@include('stiker.tambah-stiker')
@include('stiker.extend-stiker')
@include('stiker.ubah-stiker')
@include('stiker.details-stiker')
<!-- END MEMMBER MODAL -->
</div>
</div>
@@ -193,297 +202,18 @@ class="form-control"
<script src="{{asset ('vendor/sweetalert2/dist/sweetalert2.min.js') }}"></script>
<script src="{{asset ('js/custom.min.js') }}"></script>
<script src="{{asset ('js/dlabnav-init.js') }}"></script>
<script src="{{asset ('js/tooltips.js') }}"></script>
<script>
var member = @json($jenisStiker);
let selectedRowData = null;
</script>
@if(app()->environment('local'))
<script src="{{ asset('js/stikerMember/index.js') }}"></script>
<script src="{{ asset('js/stikerMember/member-register.js') }}"></script>
<script src="{{ asset('js/stikerMember/member-extend.js') }}"></script>
<script src="{{ asset('js/stikerMember/member-details.js') }}"></script>
@else
<script src="{{asset ('jx/stikermember/index.parkq.jx') }}"></script>
<script src="{{asset ('jx/stikermember/member-register.parkq.jx') }}"></script>
@endif
<script>
$(document).ready(function () {
var vehicle = @json($vehicleSelect);
let selectedRowData = null;
$('#example tbody').on('click', 'tr', function () {
console.log($('#example tbody tr').length);
const notrans = $(this).data('notrans');
// hapus semua highlight
$('#example tbody tr').removeClass('selected');
// highlight semua row dalam 1 grup
$(`#example tbody tr[data-notrans="${notrans}"]`).addClass('selected');
// ambil data dari row pertama grup
const firstRow = $(`#example tbody tr[data-notrans="${notrans}"]`).first();
selectedRowData = {
notrans : firstRow.data('notrans')
};
console.log('Selected transaksi:', selectedRowData);
$('#rowActions').removeClass('d-none');
});
$(document).on('click', '.extend-button', function () {
console.log("Tombol extend ditekan");
if (!selectedRowData) {
alert('Pilih data terlebih dahulu');
return;
}
var rawId = $(this).data('id');
// let id = rawId.substring(0, rawId.indexOf('/'));
let id = selectedRowData.notrans;
var kendaraan = vehicle;
console.log('Data ID : ', id);
var actionUrl = '{{ route("getStikerMember.show", ":id") }}';
actionUrl = actionUrl.replace(':id', "" + encodeURIComponent(id));
console.log('Action URL : ', actionUrl);
// $('#extend-member').attr('action'), actionUrl;
$.ajax({
url: actionUrl,
type: 'GET',
success: function (r) {
console.log("Datanya : ", r);
if (r) {
$('#extend-member').val('');
$('#list-nopol-ext').empty();
$('#list-card-ext').empty();
$('#notransnya').val(r.notrans);
$('#namanya').val(r.nama);
$('#address').val(r.alamat);
$('#periode').val(`${moment(r.awal).format('DD-MM-YYYY HH:mm')} - ${moment(r.akhir).format('DD-MM-YYYY HH:mm')}`);
$('#member-ext').val(r.unit_kerja);
$('#department-ext').val(r.jenis_langganan);
var rows = r.stiker_details;
if (rows.length !== 0) {
rows.forEach(row => {
var vehicleName = vehicle.find(v => v.id === row.jenis_mobil)?.nama || 'Unknown';
$('#list-nopol-ext').append(`
<tr>
<td class="text-center p-2">${row.nopol}</td>
<td class="text-center p-2" data-value="${row.jenis_mobil}">${vehicleName}</td>
<td class="text-center p-2">${row.merk}</td>
<td class="text-center p-2">${row.jenis_member}</td>
</tr>
`);
});
} else {
$('#list-nopol-ext').append(`
<tr>
<td class="text-center p-2">Tidak ada data</td>
<td class="text-center p-2">Tidak ada data</td>
<td class="text-center p-2">Tidak ada data</td>
<td class="text-center p-2">Tidak ada data</td>
</tr>
`);
}
var rowss = r.member_card;
if (rowss.length !== 0) {
rowss.forEach(ro => {
$('#list-card-ext').append(`
<tr>
<td class="text-center p-2">${ro.no_card}</td>
<td class="text-center p-2">${ro.no_urut}</td>
</tr>
`);
});
} else {
$('#list-card-ext').append(`
<tr>
<td class="text-center p-2">Tidak ada data</td>
<td class="text-center p-2">Tidak ada data</td>
</tr>
`);
}
console.log('Data : ', r);
}
},
error: function (xhr) {
console.error('Error : ', xhr);
},
});
$('#extend-member').modal('show');
});
$('.extend').on('click', function () {
// $('#modal-member-extend').modal('show');
var url = $('#extend-member').attr('action').replace(":notrans",$("#extend-member").data('notrans'));
var extend = $(this);
var formExtend = $('#extend-member').serialize();
var nopolData = [];
var cardData = [];
$('#list-nopol-ext tr').each(function () {
var row = $(this);
var rowData = {
nopol: row.find('td:nth-child(1)').text().trim(),
vehicle: row.find('td:nth-child(2)').data('value'),
member: row.find('td:nth-child(3)').text().trim(),
merk: row.find('td:nth-child(4)').text().trim(),
};
nopolData.push(rowData);
});
console.log(nopolData);
$('#list-card-ext tr').each(function () {
var row = $(this);
var rowCard = {
nocard: row.find('td:nth-child(1)').text(),
nourut: row.find('td:nth-child(2)').text(),
};
cardData.push(rowCard);
});
const stringDataExt = formExtend;
// Mengonversi query string menjadi objek
const formDataExt = {};
const searchExtend = new URLSearchParams(stringDataExt);
searchExtend.forEach((value, key) => {
formDataExt[key] = value;
});
// console.log(formDataExt);
var extData = {
formData: formDataExt,
nopolData: nopolData,
cardNumber: cardData,
};
$.ajax({
url: url,
type: 'POST',
data: extData,
headers: {
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
},
berforeSend: function () {
extend.text('Loading...');
},
success: function (data) {
if (data.response === true) {
Swal.fire({
icon: 'success',
title: 'Berhasil',
text: data.message,
});
$('#modal-member-extend').modal('hide');
} else {
Swal.fire({
icon: 'error',
title: 'Gagal',
text: data.message,
});
}
console.log(data);
},
error: function (xhr) {
console.error('Error : ', xhr);
},
});
});
$(document).on('click', '.edit-button', function () {
let id = selectedRowData;
$('#edit-member').attr('action', '/transaksi/member/' + id);
$('#ubah-member').modal('show');
// console.log(selectedRowData);
// console.log(id);
$.get(`/transaksi/member/${selectedRowData}`, function(res){
fillMemberModal(res);
$('#modal-member').modal('show');
});
});
// $('#btnEdit').on('click', function(){
// if(!selectedRowData) {
// alert('Pilih data dulu');
// return;
// }
// console.log(selectedRowData);
// $.get(`/member/${selectedRowData.notrans}`, function(res){
// fillMemberModal(res);
// $('#modal-member').modal('show');
// });
// });
function fillMemberModal(res){
let m = res.member;
// TAB 1 - DATA PENGUNJUNG
$('[name="nama"]').val(m.nama);
$('[name="address"]').val(m.alamat);
$('[name="daterange"]').val(m.awal + ' - ' + m.akhir);
$('[name="member"]').val(m.jenis_langganan).trigger('change');
$('[name="depart"]').val(m.dept).trigger('change');
$('[name="note"]').val(m.note);
$('[name="harga"]').val(m.harga);
// =========================
// TAB 2 - KENDARAAN
// =========================
$('#list-nopol').empty();
res.kendaraan.forEach(v => {
$('#list-nopol').append(`
<tr>
<td>${v.nopol}</td>
<td>${v.jenis}</td>
<td>${v.merk}</td>
<td>${v.member}</td>
<td>
<button class="btn btn-danger btn-sm remove-nopol" data-id="${v.id}">Hapus</button>
</td>
</tr>
`);
});
// =========================
// TAB 3 - KARTU
// =========================
$('#list-card').empty();
res.kartu.forEach(k => {
$('#list-card').append(`
<tr>
<td>${k.kartu}</td>
<td>${k.nourut}</td>
<td>
<button class="btn btn-danger btn-sm remove-card" data-id="${k.id}">Hapus</button>
</td>
</tr>
`);
});
}
$('#modal-member').on('hidden.bs.modal', function () {
$(this).find('form')[0].reset();
$('#list-nopol').empty();
$('#list-card').empty();
});
});
</script>
@endsection
+20 -13
View File
@@ -25,35 +25,42 @@
<div class="pt-4">
<div class="row">
<div class="col-xl-6">
<div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label">No. Transaksi</label>
<div class="col-lg-8">
<input type="text" class="form-control form-control-sm" value="OTOMATIS" readonly>
<input type="hidden" class="form-control form-control-sm"readonly>
</div>
</div>
<div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label">Jenis Transaksi</label>
<div class="col-lg-8">
<input type="text" class="form-control" value="BARU" readonly>
<input type="hidden" class="form-control" name="jenis_transaksi" value="0" readonly>
<input type="text" class="form-control form-control-sm" value="BARU" readonly>
<input type="hidden" class="form-control form-control-sm" name="jenis_transaksi" value="0" readonly>
</div>
</div>
<div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label required">Nama</label>
<div class="col-lg-8">
<input type="text" class="form-control" name="nama" placeholder="Masuk kan nama...." required>
<input type="text" class="form-control form-control-sm" name="nama" placeholder="Masuk kan nama...." required>
</div>
</div>
<div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label">Alamat</label>
<div class="col-lg-8">
<textarea type="text" class="form-control" rows="3" name="address" placeholder="JL. Hati hati raya..."></textarea>
<textarea type="text" class="form-control form-control-sm" rows="3" name="address" placeholder="JL. Hati hati raya..."></textarea>
</div>
</div>
<div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label">Telepon</label>
<div class="col-lg-8">
<input type="number" class="form-control" phone="phone" placeholder="+62 81/081...">
<input type="text" class="form-control form-control-sm" phone="phone" placeholder="+62 81/081...">
</div>
</div>
<div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label required">Periode :</label>
<div class="col-lg-8">
<input type="text" class="form-control input-daterange-timepicker" name="periode" readonly>
<input type="text" class="form-control form-control-sm input-daterange-timepicker" name="periode" readonly>
</div>
</div>
</div>
@@ -83,13 +90,13 @@
<div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label">Note :</label>
<div class="col-lg-6">
<input type="text" class="form-control" name="note" placeholder="Catatan...">
<input type="text" class="form-control form-control-sm" name="note" placeholder="Catatan...">
</div>
</div>
<div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label">Harga (Rp.):</label>
<div class="col-lg-6">
<input type="text" class="form-control" name="harga" placeholder="10.000">
<input type="text" class="form-control form-control-sm" name="harga" placeholder="10.000">
</div>
</div>
{{-- <div class="mb-3 row">
@@ -112,7 +119,7 @@
<div class="row form-material">
<div class="col-xl-3 col-md-6 mb-3">
<label class="form-label">Nomer Polisi</label>
<input type="text" class="form-control nopol-add" onkeyup="this.value = this.value.toUpperCase();" placeholder="No. Polisi">
<input type="text" class="form-control form-control-sm nopol-add" onkeyup="this.value = this.value.toUpperCase();" placeholder="No. Polisi">
<div class="invalid-feedback">Nomer Polisi sudah ada</div>
</div>
<div class="col-xl-3 col-md-6 mb-3">
@@ -131,7 +138,7 @@
</div>
<div class="col-xl-3 col-md-6 mb-3">
<label class="form-label">Merk : </label>
<input type="text" class="form-control merk" onkeyup="this.value = this.value.toUpperCase();" placeholder="Honda...">
<input type="text" class="form-control form-control-sm merk" onkeyup="this.value = this.value.toUpperCase();" placeholder="Honda...">
</div>
<div class="col-xl-3 col-md-6 mb-3">
<label class="form-label"></label>
@@ -143,8 +150,8 @@
<tr>
<th class="col-2 text-center p-2">Nomer Polisi</th>
<th class="col-2 text-center">Jenis Kendaraan</th>
<th class="col-2 text-center">Merk Kendaraan</th>
<th class="col-2 text-center">Type Member</th>
<th class="col-2 text-center">Merk Kendaraan</th>
<th class="col-2 text-center">Aksi</th>
</tr>
</thead>
@@ -160,11 +167,11 @@
<div class="row form-material">
<div class="col-xl-6 col-md-6 mb-3">
<label class="form-label">Nomer Kartu</label>
<input type="text" class="form-control kartu" placeholder="01234567...">
<input type="text" class="form-control form-control-sm kartu" placeholder="01234567...">
</div>
<div class="col-xl-6 col-md-6 mb-3">
<label class="form-label">Nomer Urut</label>
<input type="text" class="form-control nourut" placeholder="01234567...">
<input type="text" class="form-control form-control-sm nourut" placeholder="01234567...">
</div>
<hr>
{{-- <div class="table-responsive"> --}}
+6 -6
View File
@@ -10,20 +10,20 @@
<div class="custom-tab-1">
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link active" data-bs-toggle="tab" href="#home1"><i class="la la-user me-2"></i>Data Pengunjung</a>
<a class="nav-link active" data-bs-toggle="tab" href="#edit-data"><i class="la la-user me-2"></i>Data Pengunjung</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#profile1"><i class="la la-home me-2"></i> Data Kendaraan</a>
<a class="nav-link" data-bs-toggle="tab" href="#edit-vehicle"><i class="la la-home me-2"></i> Data Kendaraan</a>
</li>
<li class="nav-item">
<a class="nav-link" data-bs-toggle="tab" href="#contact1"><i class="la la-phone me-2"></i>Data Akses</a>
<a class="nav-link" data-bs-toggle="tab" href="#edit-card"><i class="la la-phone me-2"></i>Data Akses</a>
</li>
</ul>
<form class="ubah-member" id="edit-member" methode="POST">
@csrf
@method('PUT')
<div class="tab-content">
<div class="tab-pane fade show active" id="home1" role="tabpanel">
<div class="tab-pane fade show active" id="edit-data" role="tabpanel">
<div class="pt-4">
<div class="row">
<div class="col-xl-6">
@@ -98,7 +98,7 @@
</div>
</div>
</div>
<div class="tab-pane fade" id="profile1">
<div class="tab-pane fade" id="edit-vehicle">
<div class="pt-4">
<h4>Tambahkan Kendaraan</h4>
<div class="row form-material">
@@ -146,7 +146,7 @@
</div>
</div>
</div>
<div class="tab-pane fade" id="contact1">
<div class="tab-pane fade" id="edit-card">
<div class="pt-4">
<h4>Tambahkan Kartu</h4>
<div class="row form-material">
+29 -138
View File
@@ -1,148 +1,39 @@
<!DOCTYPE html>
<html>
<html lang="id">
<head>
<title>Laravel Setup</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>P-Solution Parking</title>
<style>
/* body { font-family: sans-serif; padding: 30px }
.ok { color: green }
.fail { color: red }
button { padding: 10px 20px } */
body { font-family: sans-serif; padding: 30px }
.ok { color: green }
.fail { color: red }
button {
padding: 10px 20px;
cursor: pointer;
}
#loader {
display: none;
margin-top: 20px;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #ddd;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
:root { --navy:#102a43; --blue:#1f6feb; --mint:#2dd4bf; --muted:#64748b; }
* { box-sizing:border-box; }
body { min-height:100vh; margin:0; color:var(--navy); font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif; background:radial-gradient(circle at 15% 15%,#d7f6f0 0,transparent 32%),linear-gradient(135deg,#f7fbff,#eaf1f8); }
main { width:min(960px,calc(100% - 40px)); min-height:100vh; display:grid; grid-template-columns:1.1fr .9fr; gap:48px; align-items:center; margin:auto; }
.brand { display:flex; gap:12px; align-items:center; font-size:14px; font-weight:800; letter-spacing:.05em; text-transform:uppercase; }.mark { display:grid; width:38px; height:38px; place-items:center; color:#fff; border-radius:12px; background:linear-gradient(135deg,#2877e8,#153f83); box-shadow:0 10px 25px #1f6feb38; }
h1 { margin:26px 0 16px; font-size:clamp(38px,6vw,62px); letter-spacing:-.055em; line-height:1.02; }.copy { max-width:520px; color:var(--muted); font-size:17px; line-height:1.7; }
.login { display:inline-flex; gap:10px; align-items:center; margin-top:28px; padding:14px 19px; border-radius:12px; color:#fff; text-decoration:none; font-weight:750; background:var(--navy); box-shadow:0 12px 25px #102a4328; }.login:hover { background:#163b60; }
.visual { position:relative; min-height:390px; overflow:hidden; border:1px solid #fff; border-radius:28px; background:linear-gradient(150deg,#173b65,#0e2743); box-shadow:0 25px 70px #102a4330; }.lane { position:absolute; width:190%; height:90px; left:-42%; transform:rotate(-33deg); background:repeating-linear-gradient(90deg,transparent 0 42px,#eaf4fb 42px 68px,transparent 68px 120px); opacity:.85; }.lane.one { top:105px; }.lane.two { top:250px; }.signal { position:absolute; top:52px; right:48px; width:86px; height:164px; padding:14px; border-radius:18px; background:#0b1d30; box-shadow:0 14px 28px #0005; }.signal span { display:block; width:40px; height:40px; margin:0 auto 10px; border-radius:50%; background:#25384c; }.signal span:last-child { background:#31d6a4; box-shadow:0 0 20px #31d6a4aa; }.card { position:absolute; left:32px; bottom:34px; right:32px; padding:20px; border:1px solid #ffffff24; border-radius:16px; color:#fff; background:#ffffff12; backdrop-filter:blur(8px); }.card small { color:#b5cde4; }.card strong { display:block; margin-top:7px; font-size:20px; }
@@media(max-width:760px) { main { grid-template-columns:1fr; gap:30px; padding:48px 0; }.visual { min-height:280px; }.lane.one { top:65px; }.lane.two { top:180px; } }
</style>
</head>
<body>
@if ($status['users_view'] )
<h2>🚀 Laravel Setup Checker</h2>
<h2>🚀 Laravel Setup Checker</h2>
<script>window.location = "/login";</script>
<main>
<section>
<div class="brand"><span class="mark">P</span>P-Solution Parking</div>
<h1>Operasional parkir, siap dalam satu tempat.</h1>
<p class="copy">Kelola aktivitas parkir, transaksi, member, dan pengaturan lokasi melalui sistem operasional P-Solution Parking.</p>
@if($isReady)
<a class="login" href="{{ route('login') }}">Masuk ke Aplikasi <span>→</span></a>
@else
@if(session('success'))
<p class="ok">{{ session('success') }}</p>
<p class="copy" style="font-size:14px;margin-top:24px">Aplikasi perlu disiapkan untuk database di lokasi ini sebelum dapat digunakan.</p>
<a class="login" href="{{ route('setup.index') }}">Siapkan Aplikasi <span>→</span></a>
@endif
@if(session('error'))
<p class="fail">{{ session('error') }}</p>
@endif
<ul>
<li>pgcrypto:
<b class="{{ $status['pgcrypto'] ? 'ok' : 'fail' }}">
{{ $status['pgcrypto'] ? 'OK' : 'NOT FOUND' }}
</b>
</li>
<li>pegawai.id:
<b class="{{ $status['pegawai_columns']['id'] ? 'ok' : 'fail' }}">
{{ $status['pegawai_columns']['id'] ? 'OK' : 'MISSING' }}
</b>
</li>
<li>pegawai.email:
<b class="{{ $status['pegawai_columns']['email'] ? 'ok' : 'fail' }}">
{{ $status['pegawai_columns']['email'] ? 'OK' : 'MISSING' }}
</b>
</li>
<li>pegawai.email_verified_at:
<b class="{{ $status['pegawai_columns']['email_verified_at'] ? 'ok' : 'fail' }}">
{{ $status['pegawai_columns']['email_verified_at'] ? 'OK' : 'MISSING' }}
</b>
</li>
<li>pegawai.remember_token:
<b class="{{ $status['pegawai_columns']['remember_token'] ? 'ok' : 'fail' }}">
{{ $status['pegawai_columns']['remember_token'] ? 'OK' : 'MISSING' }}
</b>
</li>
<li>users view:
<b class="{{ $status['users_view'] ? 'ok' : 'fail' }}">
{{ $status['users_view'] ? 'OK' : 'NOT FOUND' }}
</b>
</li>
<li>transaksi_parkir.veri_time:
<b class="{{ $status['veri_time'] ? 'ok' : 'fail' }}">
{{ $status['veri_time'] ? 'OK' : 'MISSING' }}
</b>
</li>
</ul>
<form method="POST" action="{{ route('setup.run') }}">
@csrf
<button type="submit">⚙️ Jalankan Setup</button>
<div id="loader">
<div class="spinner"></div>
<b>Memproses setup database…</b>
</div>
</form>
@endif
<script>
document.getElementById('runSetup').addEventListener('click', function () {
const btn = this;
const loader = document.getElementById('loader');
btn.disabled = true;
btn.innerText = 'Processing...';
loader.style.display = 'block';
fetch("{{ route('setup.run') }}", {
method: "POST",
headers: {
"X-CSRF-TOKEN": document.querySelector('meta[name="csrf-token"]').content,
"Accept": "application/json"
}
})
.then(res => res.json())
.then(res => {
if (res.success) {
loader.innerHTML = "<b class='ok'>✅ Setup selesai, mengalihkan halaman...</b>";
setTimeout(() => {
window.location.href = res.redirect;
}, 1500);
}
})
.catch(err => {
loader.innerHTML = "<b class='fail'>❌ Setup gagal</b>";
btn.disabled = false;
btn.innerText = '⚙️ Jalankan Setup';
console.error(err);
});
});
</script>
</section>
<aside class="visual" aria-hidden="true">
<div class="lane one"></div><div class="lane two"></div>
<div class="signal"><span></span><span></span><span></span></div>
<div class="card"><small>SISTEM OPERASIONAL</small><strong>Parkir lebih terukur, layanan lebih lancar.</strong></div>
</aside>
</main>
</body>
</html>
+27 -11
View File
@@ -6,10 +6,13 @@
use App\Http\Controllers\Api\V1\ConfigParameter;
use App\Http\Controllers\Api\V1\DisplayContentController;
use App\Http\Controllers\Api\V1\LaporanController;
use App\Http\Controllers\Api\V1\MemberinController;
use App\Http\Controllers\Api\V1\Memberin\MemberinController;
use App\Http\Controllers\Api\V1\ParkingOperationController;
use App\Http\Controllers\Api\V1\TransaksiMember;
use App\Http\Controllers\Api\V1\TransaksiParkir;
use App\Http\Controllers\Api\V1\LocationSiteInfoController;
use App\Http\Controllers\Api\V1\ParkingLot\ParkingLotController;
use App\Http\Controllers\Api\V1\IntercomController;
/*
|--------------------------------------------------------------------------
@@ -34,18 +37,33 @@
Route::resource("parking", ParkingOperationController::class);
Route::prefix("cashier")->group(function () {
Route::post("login", [CashierOperationController::class, "login"]);
Route::post("getmember", [
CashierOperationController::class,
"checkMember",
]);
Route::post("transaksinside", [
CashierOperationController::class,
"transaksiInside",
]);
Route::post("getmember", [CashierOperationController::class, "checkMember"]);
Route::post("transaksinside", [CashierOperationController::class, "transaksiInside"]);
// Route::middleware('CashierAuthentication')->group(function () {
// });
});
});
Route::get("location", [LocationSiteInfoController::class, "getLocation"]);
Route::resource("memberin", MemberinController::class)->names("memberin");
Route::post("memberin/register-confirm", [MemberinController::class, "registerConfirm"]);
Route::post("memberin/renew-confirm", [MemberinController::class, "renewConfirm"]);
// Parking Space
// Parking LOT Backend
// Route::resource("parking-lot", ParkingLotController::class);
Route::post("parking-lot", [ParkingLotController::class, "store"]);
Route::put("parking-lot", [ParkingLotController::class, "update"]);
// Intercom: signaling + log. Voice via Go relay UDP, bukan via PHP.
Route::prefix("intercom")->group(function () {
Route::get("devices", [IntercomController::class, "devices"]);
Route::get("queue", [IntercomController::class, "queue"]);
Route::get("history", [IntercomController::class, "history"]);
Route::post("request", [IntercomController::class, "request"]);
Route::post("accept", [IntercomController::class, "accept"]);
Route::post("end", [IntercomController::class, "end"]);
Route::post("heartbeat", [IntercomController::class, "heartbeat"]);
});
});
Route::prefix("transaksi")->group(function () {
@@ -54,8 +72,6 @@
Route::resource("parkir", TransaksiParkir::class);
});
Route::resource("member", MemberinController::class)->names("memberin");
Route::prefix("report")->group(function () {
Route::post("harian", [LaporanController::class, "harian"]);
Route::post("payment", [LaporanController::class, "payment"]);
+45 -11
View File
@@ -1,5 +1,7 @@
<?php
use App\Http\Controllers\Parking\Setup\Gate\MasterConfigNetworkController;
// use App\Http\Livewire\ParkingLotComponent;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Auth;
@@ -12,23 +14,28 @@
use App\Http\Controllers\Parking\Setup\Vehicle\ModifiedVehiclepriceController;
use App\Http\Controllers\Parking\Setup\UserLevel\LevelUserController;
use App\Http\Controllers\Parking\Setup\UserLevel\LevelMenuController;
use App\Http\Controllers\Parking\Setup\Member\StasiunKerjaController;
use App\Http\Controllers\Parking\Setup\Member\MasterTarifStikerController;
use App\Http\Controllers\Parking\LostiketController;
use App\Http\Controllers\Parking\MemberProductController;
use App\Http\Controllers\Parking\MemberStikerController;
use App\Http\Controllers\Parking\Operasional\TransaksiParkirController;
use App\Http\Controllers\Parking\Report\LaporanStiker;
use App\Http\Controllers\Parking\ReportTransaksiController;
use App\Http\Controllers\Parking\Report\ReportTransaksiController;
use App\Http\Controllers\Parking\SetupController;
use App\Http\Controllers\Parking\StikerController;
// use App\Http\Controllers\Parking\StikerController;
use App\Http\Controllers\Parking\StreamerController;
use App\Http\Controllers\Parking\FrontOffice\FrontOfficeController;
use App\Http\Controllers\Parking\Tools\StikerExtendedController;
use App\Http\Controllers\Parking\Tools\TransactionChangeController;
use App\Http\Controllers\Parking\VerifyTransController;
use App\Http\Controllers\Parking\Audit\AuditReportController;
use App\Http\Controllers\Intercom\UIController as IntercomUIController;
// Non Manufacture
use App\Http\Controllers\Apps\ModifiedTransaksi;
use App\Http\Controllers\Apps\Memberin\SelfMemberinController;
use App\Http\Controllers\ParkingLot\ParkingLotComponent;
// use App\Services\BcaSignatureTest;
/*
@@ -46,8 +53,11 @@
// return view('backend');
// });
Route::get("/", [SetupController::class, "index"]);
Route::post("/setup/run", [SetupController::class, "run"])->name("setup.run");
Route::get('/', [SetupController::class, 'index']);
Route::get('/setup', [SetupController::class, 'setup'])->name('setup.index');
Route::post('/setup/run', [SetupController::class, 'run'])
->middleware('throttle:3,1')
->name('setup.run');
Route::resource("/apps", ModifiedTransaksi::class);
Route::post("/scan", [ModifiedTransaksi::class, "scan"])->name("apps.scan");
@@ -62,18 +72,34 @@
Route::resource("gate", MasterGateController::class);
Route::resource("gate-details", MasterGateParameterController::class);
Route::resource("gate-hardware", MasterGateHardwareController::class)->names("hardware");
Route::get("dhcp", [MasterConfigNetworkController::class, "index"])->name("dhcp.index");
Route::get("/dhcp/status", [MasterConfigNetworkController::class, "status"])->name("dhcp.status");
Route::post("/dhcp/apply", [MasterConfigNetworkController::class, "apply"])->name("dhcp.apply");
Route::post("/dhcp/reservations", [MasterConfigNetworkController::class, "store"])->name(
"dhcp.reservations.store",
);
Route::get("/dhcp/logs", [MasterConfigNetworkController::class, "logs"])->name("dhcp.logs");
Route::resource("vehicle", MasterVehicleController::class);
Route::resource("price", ModifiedVehiclepriceController::class)->names("vehiclePrice");
Route::resource("level-user", LevelUserController::class)->names("levelUser");
Route::resource("level-menu", LevelMenuController::class)->names("levelMenu");
Route::get("stasiunkerja/data", [StasiunKerjaController::class, "data"])->name("stasiunkerja.data");
Route::resource("stasiunkerja", StasiunKerjaController::class)
->parameters(["stasiunkerja" => "kode"]) // ganti {stasiunkerja} jadi {kode} di URI
->except(["create", "edit"]); // gak perlu, form create/edit pakai modal di halaman index
Route::get("tarif-stiker/data", [MasterTarifStikerController::class, "data"])->name("tarif-stiker.data");
Route::resource("tarif-stiker", MasterTarifStikerController::class)
->parameters(["tarif-stiker" => "tarif_id"])
->except(["create", "edit"]);
});
});
Route::prefix("/transaksi")->group(function () {
Route::get("parkir/data", [TransaksiParkirController::class, "data"])->name("transaksiparkir.data");
Route::resource("parkir", TransaksiParkirController::class)->names("transaksiparkir");
Route::resource("stiker", MemberStikerController::class)->names("getStikerMember");
// Route::post("stiker/data", [MemberStikerController::class, "getMember"])
// ->names("getMember")
Route::post("stiker/data", [MemberStikerController::class, "getMember"])->name("getMember");
// ->where(["stiker"] => '.*');
Route::post("stiker/checkField", [MemberStikerController::class, "checkField"]);
Route::resource("member", MemberStikerController::class);
@@ -95,9 +121,15 @@
Route::get("/harian/data", [LaporanStiker::class, "dataStiker"])->name("stikerData");
});
});
Route::get("audit", [AuditReportController::class, "index"])->name("audit");
Route::get("audit/data", [AuditReportController::class, "data"])->name("audit.data");
Route::prefix("/tools")->group(function () {
Route::resource("stiker-extend", StikerExtendedController::class);
Route::resource("change-trans", TransactionChangeController::class);
Route::get("parking-lot", [ParkingLotComponent::class, "index"]);
Route::get("parking-lot/data", [ParkingLotComponent::class, "getParkingData"])->name("parking-lot.data");
// Route::get("parking-lot/data", [ParkingLotComponent::class, "data"])->name("parking-lot.data");
});
Route::prefix("/front-office")->group(function () {
@@ -115,8 +147,10 @@
Route::resource("settings-register", MemberProductController::class);
Route::get("streaming", [StreamerController::class, "index"]);
Route::get("stream-snapshot", [StreamerController::class, "streamSnapshot"]);
});
Route::get("phpinformation", function () {
phpinfo();
})->name("phpmyinfo");
// Intercom pusat: dashboard operator + download rekaman. Views di views/Intercom/UI/.
Route::prefix("intercom")->group(function () {
Route::get("/", [IntercomUIController::class, "index"])->name("intercom.ui");
Route::get("/recording/{id}", [IntercomUIController::class, "recording"])->name("intercom.recording");
});
});