diff --git a/app/Http/Controllers/Parking/Operasional/TransaksiParkirController.php b/app/Http/Controllers/Parking/Operasional/TransaksiParkirController.php
index 4233745..e1e6664 100644
--- a/app/Http/Controllers/Parking/Operasional/TransaksiParkirController.php
+++ b/app/Http/Controllers/Parking/Operasional/TransaksiParkirController.php
@@ -27,10 +27,18 @@ public function index()
* - tgl_awal / tgl_akhir (Y-m-d): periode kolom tanggal
* - status_kendaraan: 1 = masuk, 0 = keluar
* - status_transaksi: 0 = casual, 3 = member, 32 = denda
+ * - sort_by + sort_dir (asc/desc): klik header kolom
* - export=csv: unduh hasil filter sebagai CSV
*/
public function data(Request $request)
{
+ $sortable = [
+ 'id', 'no_pol', 'no_barcode', 'id_kendaraan', 'nama_kendaraan',
+ 'waktu_masuk', 'waktu_keluar', 'tanggal',
+ 'id_op_masuk', 'id_op_keluar', 'id_pintu_masuk', 'id_pintu_keluar',
+ 'bayar_keluar', 'status', 'status_transaksi',
+ ];
+
$request->validate([
'field' => 'nullable|in:id,no_pol,no_barcode',
'q' => 'nullable|string|max:100',
@@ -38,6 +46,8 @@ public function data(Request $request)
'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',
+ 'sort_by' => 'nullable|in:' . implode(',', $sortable),
+ 'sort_dir' => 'nullable|in:asc,desc',
'per_page' => 'nullable|integer|min:1|max:500',
]);
@@ -53,6 +63,7 @@ public function data(Request $request)
return response()->json([
'status' => true,
'data' => $rows->items(),
+ 'summary' => $this->summary($request),
'meta' => [
'current_page' => $rows->currentPage(),
'per_page' => $rows->perPage(),
@@ -62,6 +73,45 @@ public function data(Request $request)
]);
}
+ /**
+ * Rekap sedetail logic report (per segmen) dari filter yang sedang aktif.
+ * - keluar = status 0 ; tunai = cara_bayar null/0 ; cashless = cara_bayar 3
+ * - casual/member/denda/manual/masalah = subset tunai (ikut reportOfficer)
+ */
+ protected function summary(Request $request): array
+ {
+ $tunai = "(tp.cara_bayar IS NULL OR tp.cara_bayar = 0)";
+ $keluar = "tp.status = 0";
+
+ $query = $this->filteredQuery($request);
+ $query->reorder();
+ $query->columns = [];
+ $row = $query
+ ->selectRaw("
+ count(*) as total_rows,
+ coalesce(sum(tp.bayar_keluar), 0) as total_bayar,
+ count(case when {$keluar} then 1 end) as keluar_rows,
+ coalesce(sum(case when {$keluar} then tp.bayar_keluar end), 0) as keluar_bayar,
+ count(case when {$keluar} and {$tunai} then 1 end) as tunai_rows,
+ coalesce(sum(case when {$keluar} and {$tunai} then tp.bayar_keluar end), 0) as tunai_bayar,
+ count(case when {$keluar} and tp.cara_bayar = 3 then 1 end) as cashless_rows,
+ coalesce(sum(case when {$keluar} and tp.cara_bayar = 3 then tp.bayar_keluar end), 0) as cashless_bayar,
+ count(case when {$keluar} and {$tunai} and tp.status_transaksi = '0' and tp.id_op_masuk is not null then 1 end) as casual_rows,
+ coalesce(sum(case when {$keluar} and {$tunai} and tp.status_transaksi = '0' and tp.id_op_masuk is not null then tp.bayar_keluar end), 0) as casual_bayar,
+ count(case when {$keluar} and {$tunai} and tp.status_transaksi = '3' then 1 end) as member_rows,
+ coalesce(sum(case when {$keluar} and {$tunai} and tp.status_transaksi = '3' then tp.bayar_keluar end), 0) as member_bayar,
+ count(case when {$keluar} and {$tunai} and tp.status_transaksi = '32' then 1 end) as denda_rows,
+ coalesce(sum(case when {$keluar} and {$tunai} and tp.status_transaksi = '32' then tp.bayar_keluar end), 0) as denda_bayar,
+ count(case when {$keluar} and {$tunai} and tp.id_op_masuk is null then 1 end) as manual_rows,
+ coalesce(sum(case when {$keluar} and {$tunai} and tp.id_op_masuk is null then tp.bayar_keluar end), 0) as manual_bayar,
+ count(case when {$keluar} and {$tunai} and tp.status_transaksi = '-1' then 1 end) as masalah_rows,
+ coalesce(sum(case when {$keluar} and {$tunai} and tp.status_transaksi = '-1' then tp.bayar_keluar end), 0) as masalah_bayar
+ ")
+ ->first();
+
+ return (array) $row;
+ }
+
protected function filteredQuery(Request $request)
{
$query = DB::table('transaksi_parkir as tp')
@@ -76,7 +126,12 @@ protected function filteredQuery(Request $request)
'tp.status_transaksi',
'tp.waktu_masuk',
'tp.waktu_keluar',
- 'tp.tanggal'
+ 'tp.tanggal',
+ 'tp.id_op_masuk',
+ 'tp.id_op_keluar',
+ 'tp.id_pintu_masuk',
+ 'tp.id_pintu_keluar',
+ 'tp.bayar_keluar'
);
// 1. Pencarian: id / no_pol / no_barcode
@@ -103,7 +158,13 @@ protected function filteredQuery(Request $request)
$query->where('tp.status_transaksi', (int) $request->input('status_transaksi'));
}
- return $query->orderBy('tp.tanggal', 'desc');
+ // 5. Sorting klik header (default: tanggal terbaru)
+ $sortMap = ['nama_kendaraan' => 'jm.nama'];
+ $sortBy = $request->input('sort_by', 'tanggal');
+ $sortDir = $request->input('sort_dir', 'desc');
+ $query->orderBy($sortMap[$sortBy] ?? "tp.{$sortBy}", $sortDir);
+
+ return $query;
}
protected function exportCsv($query): StreamedResponse
@@ -112,7 +173,7 @@ protected function exportCsv($query): StreamedResponse
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']);
+ fputcsv($out, ['ID', 'No. Pol', 'Barcode', 'Kendaraan', 'Status Kendaraan', 'Status Transaksi', 'Waktu Masuk', 'Waktu Keluar', 'Op Masuk', 'Op Keluar', 'Pintu Masuk', 'Pintu Keluar', 'Bayar Keluar']);
$query->chunk(1000, function ($rows) use ($out) {
foreach ($rows as $r) {
fputcsv($out, [
@@ -124,6 +185,11 @@ protected function exportCsv($query): StreamedResponse
$r->status_transaksi,
$r->waktu_masuk,
$r->waktu_keluar,
+ $r->id_op_masuk,
+ $r->id_op_keluar,
+ $r->id_pintu_masuk,
+ $r->id_pintu_keluar,
+ $r->bayar_keluar,
]);
}
});
diff --git a/app/Http/Controllers/Parking/Setup/Petugas/MasterPetugasController.php b/app/Http/Controllers/Parking/Setup/Petugas/MasterPetugasController.php
new file mode 100644
index 0000000..b1ff256
--- /dev/null
+++ b/app/Http/Controllers/Parking/Setup/Petugas/MasterPetugasController.php
@@ -0,0 +1,186 @@
+get();
+ $levels = DB::table('level_user')->orderBy('level_code')->get();
+
+ return view('setup.petugas.petugas', compact('petugas', 'levels'));
+ }
+
+ /**
+ * Store a newly created resource in storage.
+ */
+ public function store(Request $request)
+ {
+ $data = $request->validate($this->rules(), $this->messages());
+ // Normalisasi: string kosong jadi null (kolom date/int tidak terima '').
+ $data = array_map(fn ($v) => $v === '' ? null : $v, $data);
+
+ $pegawai = Pegawai::create(collect($data)->except('foto')->all());
+
+ if ($request->hasFile('foto')) {
+ $this->saveFoto($pegawai->id, $request->file('foto'));
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'message' => 'Petugas berhasil ditambahkan.',
+ 'data' => $pegawai->fresh()->makeHidden('foto'),
+ ], 201);
+ }
+
+ /**
+ * Display the specified resource (tanpa binary foto).
+ */
+ public function show($id)
+ {
+ $pegawai = Pegawai::select(
+ 'id', 'nomer', 'nama', 'alamat', 'tgl_lahir', 'tgl_masuk',
+ 'username', 'password', 'bisalogin', 'status', 'jeniskelamin',
+ 'agama', 'telepon', 'alamat_lahir', 'alamat_sekarang', 'nip',
+ 'statusabsen', 'level_pegawai', 'email'
+ )->findOrFail($id);
+
+ $hasFoto = (bool) DB::table('pegawai')->where('id', $id)->whereNotNull('foto')->exists();
+
+ return response()->json([
+ 'success' => true,
+ 'data' => $pegawai,
+ 'has_foto' => $hasFoto,
+ ]);
+ }
+
+ /**
+ * Update the specified resource in storage.
+ */
+ public function update(Request $request, $id)
+ {
+ $pegawai = Pegawai::findOrFail($id);
+
+ $data = $request->validate($this->rules($pegawai->id), $this->messages());
+ // Normalisasi: string kosong jadi null (kolom date/int tidak terima '').
+ $data = array_map(fn ($v) => $v === '' ? null : $v, $data);
+
+ // Password kosong = tidak diubah (tetap pakai yang lama, plain-text ikut gaya legacy).
+ if (empty($data['password'])) {
+ unset($data['password']);
+ }
+
+ $pegawai->update(collect($data)->except('foto')->all());
+
+ if ($request->hasFile('foto')) {
+ $this->saveFoto($pegawai->id, $request->file('foto'));
+ }
+
+ return response()->json([
+ 'success' => true,
+ 'message' => 'Petugas berhasil diperbarui.',
+ 'data' => $pegawai->fresh()->makeHidden('foto'),
+ ]);
+ }
+
+ /**
+ * Remove the specified resource from storage.
+ */
+ public function destroy($id)
+ {
+ Pegawai::findOrFail($id)->delete();
+
+ return response()->json(['success' => true, 'message' => 'Petugas berhasil dihapus.']);
+ }
+
+ /**
+ * Stream foto dari kolom bytea. Fallback ke placeholder bila kosong.
+ */
+ public function foto($id)
+ {
+ $row = DB::table('pegawai')->select('foto')->where('id', $id)->first();
+
+ $binary = $row?->foto;
+ if (is_resource($binary)) {
+ $binary = stream_get_contents($binary);
+ }
+
+ if (empty($binary)) {
+ return response()->file(public_path('images/no-image.jpg'));
+ }
+
+ $mime = (new \finfo(FILEINFO_MIME_TYPE))->buffer($binary) ?: 'image/jpeg';
+
+ return response($binary, 200, [
+ 'Content-Type' => $mime,
+ 'Content-Length' => strlen($binary),
+ 'Cache-Control' => 'private, max-age=3600',
+ ]);
+ }
+
+ protected function messages(): array
+ {
+ return [
+ 'nomer.unique' => 'Nomer sudah dipakai petugas lain, ganti dengan yang unik.',
+ 'username.unique' => 'Username sudah dipakai petugas lain, ganti dengan yang unik.',
+ 'foto.image' => 'File foto harus berupa gambar.',
+ 'foto.max' => 'Ukuran foto maksimal 2MB.',
+ ];
+ }
+
+ protected function rules(?int $ignoreId = null): array
+ {
+ $uniqueUsername = Rule::unique('pegawai', 'username');
+ $uniqueNomer = Rule::unique('pegawai', 'nomer');
+ if ($ignoreId) {
+ $uniqueUsername->ignore($ignoreId, 'id');
+ $uniqueNomer->ignore($ignoreId, 'id');
+ }
+
+ return [
+ 'nomer' => ['required', 'string', 'max:50', $uniqueNomer],
+ 'nama' => 'required|string|max:50',
+ 'username' => ['required', 'string', 'max:50', $uniqueUsername],
+ // Plain-text ikut gaya data legacy. Wajib saat tambah, opsional saat ubah.
+ 'password' => ($ignoreId ? 'nullable' : 'required') . '|string|max:50',
+ 'level_pegawai' => 'nullable|string|exists:level_user,level_code',
+ 'bisalogin' => 'nullable|integer|in:0,1',
+ 'status' => 'nullable|integer|in:0,1',
+ 'jeniskelamin' => 'nullable|integer|in:0,1',
+ 'statusabsen' => 'nullable|integer|in:0,1',
+ 'agama' => 'nullable|string|max:50',
+ 'telepon' => 'nullable|string|max:255',
+ 'alamat' => 'nullable|string|max:100',
+ 'alamat_lahir' => 'nullable|string|max:255',
+ 'alamat_sekarang' => 'nullable|string|max:255',
+ 'nip' => 'nullable|string|max:50',
+ 'email' => 'nullable|string|max:64',
+ 'tgl_lahir' => 'nullable|date',
+ 'tgl_masuk' => 'nullable|date',
+ 'foto' => 'nullable|image|mimes:jpg,jpeg,png,bmp|max:2048',
+ ];
+ }
+
+ /**
+ * Simpan binary ke kolom bytea tanpa nambah kolom baru.
+ * bin2hex menghindari masalah escaping bytea.
+ */
+ protected function saveFoto(int $id, $file): void
+ {
+ $binary = file_get_contents($file->getRealPath());
+
+ DB::table('pegawai')->where('id', $id)->update([
+ 'foto' => DB::raw("decode('" . bin2hex($binary) . "', 'hex')"),
+ ]);
+ }
+}
diff --git a/app/Http/Controllers/Parking/Setup/Vehicle/ModifiedVehiclepriceController.php b/app/Http/Controllers/Parking/Setup/Vehicle/ModifiedVehiclepriceController.php
index f2ce4ef..0e5a34b 100644
--- a/app/Http/Controllers/Parking/Setup/Vehicle/ModifiedVehiclepriceController.php
+++ b/app/Http/Controllers/Parking/Setup/Vehicle/ModifiedVehiclepriceController.php
@@ -115,13 +115,6 @@ public function update(Request $request, $id)
$data['denda_only'] = $request->boolean('denda_only') ? 1 : 0;
$dataQuery->fill($data);
-
- if (!$dataQuery->isDirty()) {
- return response()->json([
- "status" => false,
- "message" => "Tidak ada perubahan data",
- ]);
- }
$dataQuery->save();
return response()->json([
diff --git a/app/Models/Pegawai.php b/app/Models/Pegawai.php
index 12c9858..6ae1737 100644
--- a/app/Models/Pegawai.php
+++ b/app/Models/Pegawai.php
@@ -10,6 +10,7 @@ class Pegawai extends Model implements AuthenticatableContract
{
protected $table = 'pegawai'; // Nama tabel
protected $primaryKey = 'id'; // Primary key tabel
+ public $timestamps = false; // Tabel tidak punya created_at / updated_at
protected $fillable = [
'nomer', 'nama', 'alamat', 'tgl_lahir', 'tgl_masuk', 'username', 'password', 'foto', 'bisalogin', 'status', 'jeniskelamin', 'agama', 'telepon', 'alamat_lahir', 'alamat_sekarang', 'nip', 'statusabsen', 'level_pegawai'
diff --git a/app/Services/MakeStikerServices.php b/app/Services/MakeStikerServices.php
index 36f7d05..bdb1b49 100755
--- a/app/Services/MakeStikerServices.php
+++ b/app/Services/MakeStikerServices.php
@@ -77,7 +77,7 @@ public function stikerCreate($request, Carbon $awal, Carbon $akhir)
if (!empty($dataCard)) {
foreach ($dataCard as $cardNumber) {
$insertCard = [
- "notrans" => $cardNumber["no_id"],
+ "notrans" => $cardNumber["notrans"],
"no_card" => $cardNumber["nocard"],
"no_urut" => $cardNumber["nourut"],
];
@@ -99,7 +99,7 @@ public function stikerCreate($request, Carbon $awal, Carbon $akhir)
"status" => true,
"message" => "Data berhasil di simpan",
]);
- } catch (Exception $e) {
+ } catch (\Exception $e) {
return response()->json([
"status" => false,
"message" => "Gagal menyimpan data " . $e->getMessage() . $e->getLine(),
diff --git a/public/js/member/member-register.js b/public/js/member/member-register.js
index 62dd3a2..87bd6c8 100644
--- a/public/js/member/member-register.js
+++ b/public/js/member/member-register.js
@@ -108,7 +108,7 @@ $(document).ready(function () {
$(".nopol-add").next(".invalid-feedback").hide();
if (!vehicle) {
- alert("Wajib pilih jenis kendaraan");
+ Swal.fire({icon: 'warning', title: 'Belum lengkap', text: "Wajib pilih jenis kendaraan"});
return;
}
@@ -122,7 +122,7 @@ $(document).ready(function () {
var rowhtml = `
| ${nopol} |
- ${vehicleName} |
+ ${vehicleName} |
${member} |
${merk} |
@@ -144,8 +144,8 @@ $(document).ready(function () {
console.log(r.message);
}
},
- error: function () {
- alert("Terjadi kesalahan saat memeriksa nopol");
+ error: function (xhr) {
+ Swal.fire({icon: 'error', title: 'Gagal memeriksa', text: xhr.responseJSON?.message || "Terjadi kesalahan saat memeriksa nopol"});
},
});
});
@@ -246,6 +246,9 @@ $(document).ready(function () {
console.log("FORM ADD:", allData);
if ($(".tambah-member").valid()) {
+ const saveBtn = tombol;
+ const saveHtml = saveBtn.html();
+ saveBtn.prop('disabled', true).html('Menyimpan...');
$.ajax({
url: $("#add-member").attr("action"),
type: "POST",
@@ -255,10 +258,11 @@ $(document).ready(function () {
// tombol.attr("disabled", true);
},
success: function (r) {
- // alert('Data berhasil dikirim');
- if (r.response == true) {
+ const ok = r.status == true || r.response == true;
+ if (ok) {
$("#modal-member").modal("hide");
Swal.fire({
+ icon: "success",
title: "Berhasil!!",
text: "Member berhasil di daftarkan.. !!",
timer: 5000,
@@ -267,7 +271,7 @@ $(document).ready(function () {
// Reload halaman setelah SweetAlert ditutup
// location.reload(); // Ini akan me-reload halaman
});
- } else if (r.response == false) {
+ } else {
Swal.fire({
icon: "error",
title: "Oops...",
@@ -277,7 +281,10 @@ $(document).ready(function () {
console.log(r);
},
error: function (xhr, status, error) {
- alert("Terjadi kesalahan: " + error);
+ Swal.fire({icon: 'error', title: 'Gagal menyimpan', text: xhr.responseJSON?.message || ("Terjadi kesalahan: " + error)});
+ },
+ complete: function () {
+ saveBtn.prop('disabled', false).html(saveHtml);
},
});
}
diff --git a/public/js/setup/gate.js b/public/js/setup/gate.js
index 044d64c..7cf1791 100644
--- a/public/js/setup/gate.js
+++ b/public/js/setup/gate.js
@@ -137,7 +137,7 @@
});
$('.btn-cancel').click();
- alert('Berhasil disimpan 🔥');
+ if (window.Swal) Swal.fire({icon: 'success', title: 'Berhasil', text: 'Konfigurasi hardware tersimpan.'});
}
});
});
diff --git a/public/js/setup/vehicle.js b/public/js/setup/vehicle.js
index c99345f..03f41fc 100644
--- a/public/js/setup/vehicle.js
+++ b/public/js/setup/vehicle.js
@@ -34,9 +34,13 @@ $(document).ready(function () {
$(document).on('click', '.vehicle-price', function () {
- let id = $(this).data('id');
- let nama = $(this).data('nama');
+ let btn = $(this);
+ let btnHtml = btn.html();
+ let id = btn.data('id');
+ let nama = btn.data('nama');
console.log(id);
+
+ btn.prop('disabled', true).html('Memuat...');
$.ajax({
url: '/setup/master/price/' + id,
@@ -70,6 +74,9 @@ $(document).ready(function () {
},
error: function (xhr) {
Swal.fire({icon: 'error', title: 'Gagal memuat tarif', text: xhr.responseJSON?.message || 'Data tarif tidak ditemukan.'});
+ },
+ complete: function () {
+ btn.prop('disabled', false).html(btnHtml);
}
});
});
@@ -105,6 +112,10 @@ $(document).ready(function () {
url = url.replace(':id', encodeURIComponent(id));
console.log(url);
+ const saveBtn = $(this);
+ const saveHtml = saveBtn.html();
+ saveBtn.prop('disabled', true).html('Menyimpan...');
+
$.ajax({
url: url,
type: 'PUT',
@@ -126,6 +137,9 @@ $(document).ready(function () {
return;
}
Swal.fire({icon: 'error', title: 'Gagal memperbarui tarif', text: xhr.responseJSON?.message || 'Silakan periksa kembali data tarif.'});
+ },
+ complete: function() {
+ saveBtn.prop('disabled', false).html(saveHtml);
}
});
});
diff --git a/public/js/stikerMember/member-register.js b/public/js/stikerMember/member-register.js
index 0620b95..ce8de25 100644
--- a/public/js/stikerMember/member-register.js
+++ b/public/js/stikerMember/member-register.js
@@ -108,7 +108,7 @@ $(document).ready(function () {
$(".nopol-add").next(".invalid-feedback").hide();
if (!vehicle) {
- alert("Wajib pilih jenis kendaraan");
+ Swal.fire({icon: 'warning', title: 'Belum lengkap', text: "Wajib pilih jenis kendaraan"});
return;
}
@@ -122,7 +122,7 @@ $(document).ready(function () {
var rowhtml = `
|
| ${nopol} |
- ${vehicleName} |
+ ${vehicleName} |
${member} |
${merk} |
@@ -144,8 +144,8 @@ $(document).ready(function () {
console.log(r.message);
}
},
- error: function () {
- alert("Terjadi kesalahan saat memeriksa nopol");
+ error: function (xhr) {
+ Swal.fire({icon: 'error', title: 'Gagal memeriksa', text: xhr.responseJSON?.message || "Terjadi kesalahan saat memeriksa nopol"});
},
});
});
@@ -244,21 +244,23 @@ $(document).ready(function () {
};
if ($(".tambah-member").valid()) {
+ const saveHtml = tombol.html();
$.ajax({
url: $("#add-member").attr("action"),
type: "POST",
data: allData,
headers: { "X-CSRF-token": $('meta[name="csrf-token"]').attr("content") },
beforeSend: function () {
- tombol.attr("disabled", true);
+ tombol.prop("disabled", true).html('Menyimpan...');
},
success: function (r) {
// alert('Data berhasil dikirim');
$("#modal-member").modal("hide");
if (r.status == true) {
Swal.fire({
+ icon: "success",
title: "Berhasil!!",
- text: "Member anda telah terdaftar !!",
+ text: r.message || "Member anda telah terdaftar !!",
timer: 5000,
showConfirmButton: !1,
}).then(function () {
@@ -266,12 +268,15 @@ $(document).ready(function () {
location.reload(); // Ini akan me-reload halaman
});
} else if (r.status == 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);
},
error: function (xhr, status, error) {
- alert("Terjadi kesalahan: " + error);
+ Swal.fire({icon: 'error', title: 'Gagal menyimpan', text: xhr.responseJSON?.message || ("Terjadi kesalahan: " + error)});
+ },
+ complete: function () {
+ tombol.prop('disabled', false).html(saveHtml);
},
});
}
diff --git a/resources/views/Parking/setup/member/stasiunkerja.blade.php b/resources/views/Parking/setup/member/stasiunkerja.blade.php
index d83f4b6..69d8a55 100644
--- a/resources/views/Parking/setup/member/stasiunkerja.blade.php
+++ b/resources/views/Parking/setup/member/stasiunkerja.blade.php
@@ -139,5 +139,6 @@
// tidak hardcode URL (menghindari masalah beda host/port seperti sebelumnya).
window.STASIUNKERJA_ENDPOINT = "{{ url('/setup/master/stasiunkerja') }}";
+
@endsection
diff --git a/resources/views/Parking/setup/member/tarifstiker.blade.php b/resources/views/Parking/setup/member/tarifstiker.blade.php
index a312843..410c8c8 100644
--- a/resources/views/Parking/setup/member/tarifstiker.blade.php
+++ b/resources/views/Parking/setup/member/tarifstiker.blade.php
@@ -146,5 +146,6 @@
+
@endsection
diff --git a/resources/views/layouts/partial/horizontal-menu.blade.php b/resources/views/layouts/partial/horizontal-menu.blade.php
index 88d6936..a8e6d68 100644
--- a/resources/views/layouts/partial/horizontal-menu.blade.php
+++ b/resources/views/layouts/partial/horizontal-menu.blade.php
@@ -29,7 +29,7 @@
Master Shift
Master Level Pengguna
- Master Petugas/Pegawai
+ Master Petugas/Pegawai
Master Stiker
diff --git a/resources/views/operasional/transaksi-parkir.blade.php b/resources/views/operasional/transaksi-parkir.blade.php
index ad19e4c..b105877 100644
--- a/resources/views/operasional/transaksi-parkir.blade.php
+++ b/resources/views/operasional/transaksi-parkir.blade.php
@@ -25,6 +25,17 @@
.table td {
text-align: right;
} */
+ #tabel-parkir th[data-sort] {
+ cursor: pointer;
+ white-space: nowrap;
+ user-select: none;
+ }
+ #tabel-parkir th[data-sort]:hover {
+ background-color: #f2f6ff;
+ }
+ #tabel-parkir .sort-ind {
+ font-size: 11px;
+ }
@endsection
@@ -117,6 +128,17 @@ class="btn btn-sm btn-icon btn-danger btn-export-pdf" title="Cetak">
+
Hasil Transaksi
@@ -126,18 +148,23 @@ class="btn btn-sm btn-icon btn-danger btn-export-pdf" title="Cetak">
| No. |
- ID |
- No. Pol |
- Barcode |
- Kendaraan |
- Waktu Masuk |
- Waktu Keluar |
- Status Kendaraan |
- Status Transaksi |
+ ID |
+ No. Pol |
+ Barcode |
+ Kendaraan |
+ Waktu Masuk |
+ Waktu Keluar |
+ Op Masuk |
+ Op Keluar |
+ Pintu Masuk |
+ Pintu Keluar |
+ Bayar Keluar |
+ Status Kendaraan |
+ Status Transaksi |
|
- | Memuat data... |
+ | Memuat data... |
@@ -162,6 +189,8 @@ class="btn btn-sm btn-icon btn-danger btn-export-pdf" title="Cetak">
$(function () {
const dataUrl = '{{ route('transaksiparkir.data', [], false) }}';
let page = 1;
+ let sortBy = 'tanggal';
+ let sortDir = 'desc';
const labelKendaraan = {1: 'Masuk', 0: 'Keluar'};
const labelTransaksi = {0: 'Casual', 3: 'Member', 32: 'Denda'};
@@ -196,23 +225,62 @@ function params(extra) {
tgl_akhir: $('#tgl_akhir').val(),
status_kendaraan: $('#status_kendaraan').val(),
status_transaksi: $('#status_transaksi').val(),
+ sort_by: sortBy,
+ sort_dir: sortDir,
page: page
};
return $.param(Object.assign(p, extra || {}));
}
+ function paintSort() {
+ $('#tabel-parkir th[data-sort] .sort-ind').text('');
+ const th = $('#tabel-parkir th[data-sort="' + sortBy + '"] .sort-ind');
+ if (th.length) th.text(sortDir === 'asc' ? 'â–²' : 'â–¼');
+ }
+
+ $('#tabel-parkir').on('click', 'th[data-sort]', function () {
+ const col = $(this).data('sort');
+ if (sortBy === col) {
+ sortDir = sortDir === 'asc' ? 'desc' : 'asc';
+ } else {
+ sortBy = col;
+ sortDir = 'asc';
+ }
+ page = 1;
+ paintSort();
+ load();
+ });
+
+ function fmtRp(v) {
+ if (v === null || v === undefined || v === '') return '';
+ return 'Rp ' + Number(v).toLocaleString('id-ID');
+ }
+
+ function paintSummary(s) {
+ if (!s) return;
+ const segs = ['total', 'keluar', 'tunai', 'cashless', 'casual', 'member', 'denda', 'manual', 'masalah'];
+ segs.forEach(function (k) {
+ $('#sum-' + k).text((Number(s[k + '_rows']) || 0).toLocaleString('id-ID') + ' trx');
+ $('#sum-' + k + '-rp').text(fmtRp(s[k + '_bayar']));
+ });
+ }
+
function load() {
- $('#body-parkir').html('| Memuat data... |
');
+ $('#body-parkir').html('| Memuat data... |
');
+ $('#btn-cari').prop('disabled', true).html('Mencari...');
+ $('#btn-prev, #btn-next').prop('disabled', true);
$.ajax({
url: dataUrl + '?' + params(),
type: 'GET',
success: function (r) {
+ paintSummary(r.summary);
if (!r.data.length) {
- $('#body-parkir').html('| Data tidak ditemukan. |
');
+ $('#body-parkir').html('| Data tidak ditemukan. |
');
} else {
let html = '';
const offset = (r.meta.current_page - 1) * r.meta.per_page;
r.data.forEach(function (row, i) {
+ const bayar = row.bayar_keluar === null || row.bayar_keluar === undefined || row.bayar_keluar === '' ? '' : Number(row.bayar_keluar).toLocaleString('id-ID');
html += ''
+ '| ' + (offset + i + 1) + ' | '
+ '' + esc(row.id) + ' | '
@@ -221,6 +289,11 @@ function load() {
+ '' + esc([row.id_kendaraan, row.nama_kendaraan].filter(Boolean).join(' - ')) + ' | '
+ '' + esc(row.waktu_masuk) + ' | '
+ '' + esc(row.waktu_keluar) + ' | '
+ + '' + esc(row.id_op_masuk) + ' | '
+ + '' + esc(row.id_op_keluar) + ' | '
+ + '' + esc(row.id_pintu_masuk) + ' | '
+ + '' + esc(row.id_pintu_keluar) + ' | '
+ + '' + bayar + ' | '
+ '' + statusKendaraan(row.status) + ' | '
+ '' + statusTransaksi(row.status_transaksi) + ' | '
+ '
';
@@ -238,7 +311,11 @@ function load() {
if (xhr.status === 422 && xhr.responseJSON?.errors) {
msg = Object.values(xhr.responseJSON.errors).flat().join(' ');
}
- $('#body-parkir').html('| ' + esc(msg) + ' |
');
+ $('#body-parkir').html('| ' + esc(msg) + ' |
');
+ Swal.fire({icon: 'error', title: 'Gagal memuat data', text: msg});
+ },
+ complete: function () {
+ $('#btn-cari').prop('disabled', false).html('Cari');
}
});
}
@@ -258,6 +335,7 @@ function load() {
});
$('.btn-export-pdf').on('click', function () { window.print(); });
+ paintSort();
load();
});
diff --git a/resources/views/setup/UserLevel/level-user.blade.php b/resources/views/setup/UserLevel/level-user.blade.php
index 278f40c..5409067 100644
--- a/resources/views/setup/UserLevel/level-user.blade.php
+++ b/resources/views/setup/UserLevel/level-user.blade.php
@@ -178,7 +178,7 @@
});
$('.btn-cancel').click();
- alert('Berhasil disimpan 🔥');
+ Swal.fire({icon: 'success', title: 'Berhasil', text: 'Level pengguna tersimpan.'});
}
});
});
diff --git a/resources/views/setup/gate/gate.blade.php b/resources/views/setup/gate/gate.blade.php
index f0be237..08c0f56 100644
--- a/resources/views/setup/gate/gate.blade.php
+++ b/resources/views/setup/gate/gate.blade.php
@@ -155,6 +155,8 @@
$(document).ready(function () {
$(document).on('click', '.hardware-button', function () {
+ let btn = $(this);
+ let btnHtml = btn.html();
let row = $(this).closest('tr');
// kalau sudah ada baris edit, hapus dulu
@@ -166,6 +168,8 @@
// # base url tanpa parameter biar tidak dobel slash
let baseUrl = "{{ url('setup/master/gate-hardware') }}";
+ btn.prop('disabled', true).html('Memuat...');
+
$.ajax({
url: baseUrl + "/" + id,
type: 'GET',
@@ -244,6 +248,10 @@ class="form-control form-control-sm edit-mode d-none"
},
error: function (xhr) {
console.error(xhr.responseText);
+ Swal.fire({icon: 'error', title: 'Gagal memuat hardware', text: xhr.responseJSON?.message || 'Cek koneksi / hubungi admin.'});
+ },
+ complete: function () {
+ btn.prop('disabled', false).html(btnHtml);
}
});
});
@@ -275,6 +283,8 @@ class="form-control form-control-sm edit-mode d-none"
// SAVE
$(document).on('click', '.btn-save', function () {
+ const btn = $(this);
+ const origHtml = btn.html();
let dataToSend = {};
let gateId = $('.edit-row').data('id');
@@ -289,6 +299,8 @@ class="form-control form-control-sm edit-mode d-none"
// # base url tanpa parameter biar tidak dobel slash
let baseUrl = "{{ url('setup/master/gate-hardware') }}";
+ btn.prop('disabled', true).html('Menyimpan...');
+
$.ajax({
url: baseUrl + "/" + gateId,
type: "POST",
@@ -308,11 +320,14 @@ class="form-control form-control-sm edit-mode d-none"
});
$('.btn-cancel').click();
- alert('Berhasil disimpan');
+ Swal.fire({icon: 'success', title: 'Berhasil', text: 'Konfigurasi hardware tersimpan.'});
},
error: function (xhr) {
console.error(xhr.responseText);
- alert('Gagal menyimpan. Cek koneksi / hubungi admin.');
+ Swal.fire({icon: 'error', title: 'Gagal menyimpan', text: xhr.responseJSON?.message || 'Cek koneksi / hubungi admin.'});
+ },
+ complete: function () {
+ btn.prop('disabled', false).html(origHtml);
}
});
});
diff --git a/resources/views/setup/index.blade.php b/resources/views/setup/index.blade.php
index a77d689..b5bb204 100644
--- a/resources/views/setup/index.blade.php
+++ b/resources/views/setup/index.blade.php
@@ -598,6 +598,7 @@ class="btn btn-primary"
logsUrl: @json(route('dhcp.logs')),
};
+
@endsection
diff --git a/resources/views/setup/petugas/petugas.blade.php b/resources/views/setup/petugas/petugas.blade.php
new file mode 100644
index 0000000..1bd2b33
--- /dev/null
+++ b/resources/views/setup/petugas/petugas.blade.php
@@ -0,0 +1,286 @@
+@extends('layouts.master')
+
+@section('styles')
+
+@endsection
+
+@section('content')
+
+
+
+
+
+
+
+
+
Master Petugas / Pegawai
+
+
+
+
+
+
+ | Foto |
+ Nomer |
+ Nama |
+ Username |
+ Level |
+ Bisa Login |
+ Status |
+ Aksi |
+
+
+
+ @forelse ($petugas as $p)
+
+
+
+ |
+ {{ $p->nomer }} |
+ {{ $p->nama }} |
+ {{ $p->username }} |
+ {{ $levels->firstWhere('level_code', $p->level_pegawai)->level_name ?? $p->level_pegawai }} |
+
+
+ {{ $p->bisalogin == 1 ? 'Ya' : 'Tidak' }}
+
+ |
+
+
+ {{ $p->status == 1 ? 'Aktif' : 'Nonaktif' }}
+
+ |
+
+
+
+
+ |
+
+ @empty
+ | Belum ada data petugas. |
+ @endforelse
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+@endsection
+
+@section('javascript')
+
+
+
+@endsection
diff --git a/resources/views/setup/vehicle/vehicle.blade.php b/resources/views/setup/vehicle/vehicle.blade.php
index 635089e..cee086d 100644
--- a/resources/views/setup/vehicle/vehicle.blade.php
+++ b/resources/views/setup/vehicle/vehicle.blade.php
@@ -173,11 +173,21 @@ function formRow(mode, vehicle) {
$(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.') });
+ const btn = form.find('[type="submit"]'); const btnHtml = btn.html();
+ btn.prop('disabled', true).html('Menyimpan...');
+ $.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 => {
+ btn.prop('disabled', false).html(btnHtml);
+ Swal.fire({icon: 'error', title: 'Gagal menyimpan', text: 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('Memuat detail...
'); 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(`- ID Kendaraan
- ${r.data.id}
- Nama
- ${r.data.nama}
- Shortcut
- ${r.data.short_cut}
- Tarif Awal
- ${t ? Number(t.tarif || 0).toLocaleString('id-ID') : '-'}
- Tarif Interval
- ${t ? Number(t.tarif_interval || 0).toLocaleString('id-ID') : '-'}
- Waktu Gratis
- ${t?.waktu_gratis ?? '-' } menit
`); }); });
+ $('#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', showLoaderOnConfirm: true, preConfirm: () => $.ajax({url: routes.destroy.replace(':id', encodeURIComponent(selected.vehicleId)), type:'DELETE', headers:{'X-CSRF-TOKEN':csrf}}).catch(xhr => { Swal.showValidationMessage(xhr.responseJSON?.message || 'Kendaraan tidak dapat dihapus.'); })}).then(result => { if (result.isConfirmed && result.value) { Swal.fire({icon: 'success', title: 'Berhasil', text: 'Kendaraan dihapus.', timer: 1500, showConfirmButton: false}).then(() => window.location.reload()); } }); });
+ $('#btnDetail').on('click', function () { if (!selected) return; const btn = $(this); const btnHtml = btn.html(); btn.prop('disabled', true).html('Memuat...'); $('#vehicle-detail-body').html('Memuat detail...
'); 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(`- ID Kendaraan
- ${r.data.id}
- Nama
- ${r.data.nama}
- Shortcut
- ${r.data.short_cut}
- Tarif Awal
- ${t ? Number(t.tarif || 0).toLocaleString('id-ID') : '-'}
- Tarif Interval
- ${t ? Number(t.tarif_interval || 0).toLocaleString('id-ID') : '-'}
- Waktu Gratis
- ${t?.waktu_gratis ?? '-' } menit
`); }).fail(xhr => { $('#vehicle-detail-body').html('' + (xhr.responseJSON?.message || 'Gagal memuat detail.') + '
'); }).always(() => btn.prop('disabled', false).html(btnHtml)); });
});
diff --git a/resources/views/stiker/extend-stiker.blade.php b/resources/views/stiker/extend-stiker.blade.php
index 97ed96a..cb98b62 100644
--- a/resources/views/stiker/extend-stiker.blade.php
+++ b/resources/views/stiker/extend-stiker.blade.php
@@ -54,7 +54,7 @@
diff --git a/resources/views/stiker/stiker.blade.php b/resources/views/stiker/stiker.blade.php
index b824f0a..b644a08 100644
--- a/resources/views/stiker/stiker.blade.php
+++ b/resources/views/stiker/stiker.blade.php
@@ -39,6 +39,33 @@
.table-selectable tbody tr.selected td {
color: #fff !important;
}
+
+ /* Tegaskan garis input di modal member (tambah/ubah/extend/detail).
+ Cuma warna border (tetap 1px), layout tidak berubah. */
+ #modal-member .form-control,
+ #modal-member .form-select,
+ #modal-extend .form-control,
+ #modal-extend .form-select,
+ #ubah-member .form-control,
+ #ubah-member .form-select,
+ #modal-details .form-control {
+ border-color: #9aa5b1;
+ }
+
+ /* Template global bikin border .table-bordered 5px — balikin ke 1px
+ khusus tabel di modal member. */
+ #modal-member .table-bordered > :not(caption) > *,
+ #modal-extend .table-bordered > :not(caption) > *,
+ #ubah-member .table-bordered > :not(caption) > *,
+ #modal-details .table-bordered > :not(caption) > * {
+ border-width: 1px 0;
+ }
+ #modal-member .table-bordered > :not(caption) > * > *,
+ #modal-extend .table-bordered > :not(caption) > * > *,
+ #ubah-member .table-bordered > :not(caption) > * > *,
+ #modal-details .table-bordered > :not(caption) > * > * {
+ border-width: 0 1px;
+ }
@endsection
diff --git a/resources/views/stiker/tambah-stiker.blade.php b/resources/views/stiker/tambah-stiker.blade.php
index 3efb164..3b53cfa 100644
--- a/resources/views/stiker/tambah-stiker.blade.php
+++ b/resources/views/stiker/tambah-stiker.blade.php
@@ -54,7 +54,7 @@
diff --git a/resources/views/stiker/ubah-stiker.blade.php b/resources/views/stiker/ubah-stiker.blade.php
index e3014c4..c0f1bf5 100644
--- a/resources/views/stiker/ubah-stiker.blade.php
+++ b/resources/views/stiker/ubah-stiker.blade.php
@@ -49,7 +49,7 @@
diff --git a/routes/web.php b/routes/web.php
index 0dd03a1..c5de9b1 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -12,6 +12,7 @@
use App\Http\Controllers\Parking\Setup\Gate\MasterGateParameterController;
use App\Http\Controllers\Parking\Setup\Vehicle\MasterVehicleController;
use App\Http\Controllers\Parking\Setup\Vehicle\ModifiedVehiclepriceController;
+use App\Http\Controllers\Parking\Setup\Petugas\MasterPetugasController;
use App\Http\Controllers\Parking\Setup\UserLevel\LevelUserController;
use App\Http\Controllers\Parking\Setup\UserLevel\LevelMenuController;
use App\Http\Controllers\Parking\Setup\Member\StasiunKerjaController;
@@ -81,6 +82,8 @@
Route::get("/dhcp/logs", [MasterConfigNetworkController::class, "logs"])->name("dhcp.logs");
Route::resource("vehicle", MasterVehicleController::class);
Route::resource("price", ModifiedVehiclepriceController::class)->names("vehiclePrice");
+ Route::get("petugas/{petugas}/foto", [MasterPetugasController::class, "foto"])->name("petugas.foto");
+ Route::resource("petugas", MasterPetugasController::class)->names("petugas");
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");