Ubah/extend in-place data lama + search semua-field + statistik hari-minggu-bulan

This commit is contained in:
2026-10-15 14:20:34 +07:00
parent fb9a688d28
commit 411d365366
10 changed files with 1925 additions and 25 deletions
@@ -46,11 +46,15 @@ public function getMember(Request $request)
// 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", ""));
// Nama param tidak konsisten antar JS (search-by vs searchBy, dst) -> terima dua-duanya.
$val = fn($a, $b, $c = null) => $request->filled($a) ? $request->input($a)
: ($request->filled($b) ? $request->input($b)
: ($c && $request->filled($c) ? $request->input($c) : null));
$searchBy = $val("search-by", "searchBy");
$searchValue = trim($val("search_value", "searchValue") ?? "");
$memberType = $request->input("member_type");
$unitKerja = $request->input("departement");
$memberType = $val("member_type", "memberType", "jenismember");
$unitKerja = $val("departement", "department", "depart");
$vehicle = $request->input("vehicle");
$isActive = $request->input("isActive");
$tglAwal = $request->input("tgl_awal");
@@ -89,21 +93,30 @@ public function getMember(Request $request)
$dataStiker->where("ts.nama", "ILIKE", "%{$searchValue}%");
break;
case "alamat":
$dataStiker->where("ts.alamat", "like", "%{$searchValue}%");
$dataStiker->where("ts.alamat", "ILIKE", "%{$searchValue}%");
break;
case "nopol":
$dataStiker->where("dts.nopol", "like", "%{$searchValue}%");
$dataStiker->where("dts.nopol", "ILIKE", "%{$searchValue}%");
break;
case "nocard":
$dataStiker->where("cm.nocard", "like", "%{$searchValue}%");
$dataStiker->where("cm.no_card", "ILIKE", "%{$searchValue}%");
break;
case "notrans":
$dataStiker->where("ts.notrans", "like", "%{$searchValue}%");
$dataStiker->where("ts.notrans", "ILIKE", "%{$searchValue}%");
break;
// case "":
// $dataStiker->where("ts.nama", "like", "%{$searchValue}%");
// break;
}
} elseif (!$searchBy && $searchValue !== "") {
// Dropdown "Semua" + ada kata kunci: cari ke semua field teks.
$dataStiker->where(function ($w) use ($searchValue) {
$w->where("ts.nama", "ILIKE", "%{$searchValue}%")
->orWhere("ts.alamat", "ILIKE", "%{$searchValue}%")
->orWhere("dts.nopol", "ILIKE", "%{$searchValue}%")
->orWhere("cm.no_card", "ILIKE", "%{$searchValue}%")
->orWhere("ts.notrans", "ILIKE", "%{$searchValue}%");
});
}
if ($memberType) {
@@ -374,6 +387,7 @@ public function store(Request $request, MakeStikerServices $makeStiker)
public function show($id)
{
Log::info("Data yang diterima : " . json_encode($id));
// Exact match (dulu LIKE %..% bisa nyantol baris lain, fatal untuk notrans lama).
$dataStiker = StikerTrans
// with("stiker_details.vehicle", "memberCard")
::select(
@@ -394,9 +408,13 @@ public function show($id)
)
->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 . "%")
->where("notrans", $id)
->first();
if (!$dataStiker) {
return response()->json(["message" => "Data tidak ditemukan."], 404);
}
$vehicleDetails = StikerTransDetail::query()
->from("detail_transaksi_stiker as dts")
->select("jm.nama as vehicle", "ts.tarif", "dts.*")
@@ -446,9 +464,189 @@ public function edit($id)
* @param int $id
* @return \Illuminate\Http\Response
*/
/**
* Update di tempat (nomor tidak berubah). Dipakai modal Ubah untuk
* data lama maupun baru. Detail kendaraan & kartu diganti sesuai
* list yang dikirim (roundtrip pakai kunci yang sama).
*/
public function update(Request $request, $id)
{
//
$trx = DB::table("transaksi_stiker")->where("notrans", $id)->first();
if (!$trx) {
return response()->json(["status" => false, "message" => "Data tidak ditemukan."], 404);
}
$data = $request->validate([
"nama" => "sometimes|string|max:255",
"address" => "nullable|string",
"phone" => "nullable|string|max:64",
"member" => "nullable|string",
"depart" => "nullable|string",
"note" => "nullable|string",
"harga" => "nullable|numeric",
"periode" => "nullable|string",
"daterange" => "nullable|string",
"awal" => "nullable|string",
"akhir" => "nullable|string",
"nopolData" => "nullable|array",
"cardNumber" => "nullable|array",
]);
return DB::transaction(function () use ($trx, $data, $id) {
$upd = [];
if (array_key_exists("nama", $data)) {
$upd["nama"] = $data["nama"];
}
if (array_key_exists("address", $data)) {
$upd["alamat"] = $data["address"] ?? "";
}
if (array_key_exists("phone", $data)) {
$upd["telepon"] = $data["phone"] ?? "";
}
if (array_key_exists("member", $data)) {
$upd["jenis_langganan"] = $data["member"];
}
if (array_key_exists("depart", $data)) {
$upd["unit_kerja"] = $data["depart"];
}
if (array_key_exists("note", $data)) {
$upd["keterangan"] = $data["note"] ?? "";
}
if (array_key_exists("harga", $data)) {
$upd["harga"] = $data["harga"] ?? 0;
}
$periode = $data["periode"] ?? $data["daterange"] ?? null;
if ($periode && str_contains($periode, " - ")) {
[$p0, $p1] = explode(" - ", $periode, 2);
$upd["awal"] = Carbon::createFromFormat("d-m-Y H:i", trim($p0));
$upd["akhir"] = Carbon::createFromFormat("d-m-Y H:i", trim($p1));
} else {
if (!empty($data["awal"])) {
$upd["awal"] = $data["awal"];
}
if (!empty($data["akhir"])) {
$upd["akhir"] = $data["akhir"];
}
}
$upd["tgl_edited"] = now();
if (!empty($upd)) {
DB::table("transaksi_stiker")->where("notrans", $id)->update($upd);
}
// Ganti detail kendaraan (kunci notrans yang sama, nomor dipertahankan).
if (array_key_exists("nopolData", $data)) {
DB::table("detail_transaksi_stiker")->where("notrans", $id)->delete();
foreach ($data["nopolData"] ?? [] as $nopol) {
if (empty($nopol["nopol"])) {
continue;
}
$produk = explode(" - ", $nopol["member"] ?? "");
DB::table("detail_transaksi_stiker")->insert([
"notrans" => $id,
"nopol" => $nopol["nopol"],
"jenis_mobil" => $nopol["vehicle"] ?? null,
"jenis_member" => $produk[0] ?? null,
"merk" => $nopol["merk"] ?? null,
]);
}
}
// Ganti kartu (kunci no_id yang sama). Lewati bila no_id kosong (data lama tertentu).
if (array_key_exists("cardNumber", $data) && !empty($trx->no_id)) {
DB::table("card_member")->where("notrans", $trx->no_id)->delete();
foreach ($data["cardNumber"] ?? [] as $card) {
if (empty($card["nocard"])) {
continue;
}
DB::table("card_member")->insert([
"notrans" => $trx->no_id,
"no_card" => $card["nocard"],
"no_urut" => $card["nourut"] ?? null,
]);
}
}
return response()->json(["status" => true, "message" => "Data berhasil diperbarui, nomor tetap " . $id . "."]);
});
}
/**
* Perpanjang notrans LAMA di tempat (nomor dipertahankan).
* Header periode (+ field lain bila dikirim) diupdate; kendaraan
* BARU (flag _baru) ditempel ke notrans yang sama; kartu tidak diutak-atik.
*/
public function extendOld(Request $request)
{
$notrans = $request->input("notrans");
$trx = DB::table("transaksi_stiker")->where("notrans", $notrans)->first();
if (!$trx) {
return response()->json(["status" => false, "message" => "Data tidak ditemukan."], 404);
}
$data = $request->validate([
"notrans" => "required|string",
"nama" => "sometimes|string|max:255",
"address" => "nullable|string",
"phone" => "nullable|string|max:64",
"member" => "nullable|string",
"depart" => "nullable|string",
"note" => "nullable|string",
"harga" => "nullable|numeric",
"periode" => "nullable|string",
"daterange" => "nullable|string",
"awal" => "nullable|string",
"akhir" => "nullable|string",
"nopolData" => "nullable|array",
]);
return DB::transaction(function () use ($trx, $data, $notrans) {
$upd = ["jenis_transaksi" => "1", "tgl_edited" => now()];
foreach (["nama" => "nama", "address" => "alamat", "phone" => "telepon",
"member" => "jenis_langganan", "depart" => "unit_kerja",
"note" => "keterangan", "harga" => "harga"] as $in => $col) {
if (array_key_exists($in, $data)) {
$upd[$col] = $data[$in] ?? "";
}
}
$periode = $data["periode"] ?? $data["daterange"] ?? null;
if ($periode && str_contains($periode, " - ")) {
[$p0, $p1] = explode(" - ", $periode, 2);
$upd["awal"] = Carbon::createFromFormat("d-m-Y H:i", trim($p0));
$upd["akhir"] = Carbon::createFromFormat("d-m-Y H:i", trim($p1));
} else {
if (!empty($data["awal"])) {
$upd["awal"] = $data["awal"];
}
if (!empty($data["akhir"])) {
$upd["akhir"] = $data["akhir"];
}
}
DB::table("transaksi_stiker")->where("notrans", $notrans)->update($upd);
// Tempel kendaraan BARU saja (baris display lama dilewati via flag _baru).
$tambah = 0;
foreach ($data["nopolData"] ?? [] as $nopol) {
if (empty($nopol["_baru"]) || empty($nopol["nopol"])) {
continue;
}
$produk = explode(" - ", $nopol["member"] ?? "");
DB::table("detail_transaksi_stiker")->insert([
"notrans" => $notrans,
"nopol" => $nopol["nopol"],
"jenis_mobil" => $nopol["vehicle"] ?? null,
"jenis_member" => $produk[0] ?? null,
"merk" => $nopol["merk"] ?? null,
]);
$tambah++;
}
return response()->json(["status" => true,
"message" => "Perpanjangan tersimpan, nomor tetap " . $notrans . " ({$tambah} kendaraan baru)."]);
});
}
/**
File diff suppressed because it is too large Load Diff
+196
View File
@@ -309,4 +309,200 @@ $(document).ready(function () {
});
}
});
// ================= UBAH (update di tempat, nomor dipertahankan) =================
function updateHargaUbah() {
let total = 0;
$("#ubah-member #edit-list-nopol tr").each(function () {
total += parseFloat($(this).data("tarif")) || 0;
});
$("#ubah-member input[name='harga']").val(total);
}
function showUrlFor(notrans) {
const tpl = $("#btnExtend").data("url") || "/transaksi/stiker/:id";
return tpl.replace(":id", encodeURIComponent(notrans));
}
$(document).on("click", ".edit-button", function () {
const notrans = (typeof selectedRowData !== "undefined" && selectedRowData?.notrans)
|| $(this).data("id") || "";
if (!notrans) {
Swal.fire({icon: "warning", title: "Belum pilih data", text: "Klik salah satu baris transaksi dulu."});
return;
}
$.ajax({
url: showUrlFor(notrans),
type: "GET",
success: function (r) {
const m = $("#ubah-member");
m.find("#edit-notrans").val(r.notrans);
m.find("[name='nama']").val(r.nama ?? "");
m.find("[name='address']").val(r.alamat ?? "");
m.find("[name='phone']").val(r.telepon ?? "");
m.find("[name='note']").val(r.keterangan ?? "");
m.find("[name='harga']").val(r.harga ?? 0);
m.find("[name='member']").val(r.jenis_langganan ?? "");
m.find("[name='depart']").val(r.unit_kerja ?? "");
if (r.awal && r.akhir) {
m.find("[name='daterange']").val(
`${moment(r.awal).format("DD-MM-YYYY HH:mm")} - ${moment(r.akhir).format("DD-MM-YYYY HH:mm")}`,
);
}
const jl = $("#edit-list-nopol").empty();
(r.stiker_details || []).forEach((row) => {
jl.append(`
<tr data-tarif="${row.tarif ?? 0}">
<td class="text-center">${row.nopol ?? ""}</td>
<td class="text-center" data-value="${row.jenis_mobil ?? ""}">${row.vehicle ?? ""}</td>
<td class="text-center">${row.jenis_member ?? ""}</td>
<td class="text-center">${row.merk ?? ""}</td>
<td class="text-center p-1"><button class="btn btn-danger btn-xs edit-del-nopol" type="button">Hapus</button></td>
</tr>`);
});
const kl = $("#edit-list-card").empty();
(r.memberCard || []).forEach((ro) => {
kl.append(`
<tr>
<td class="text-center">${ro.no_card ?? ""}</td>
<td class="text-center">${ro.no_urut ?? ""}</td>
<td class="text-center p-2"><button class="btn btn-danger btn-xs edit-del-card" type="button">Hapus</button></td>
</tr>`);
});
updateHargaUbah();
},
error: function (xhr) {
Swal.fire({icon: "error", title: "Gagal memuat data", text: xhr.responseJSON?.message || "Data tidak ditemukan."});
},
});
});
$("#edit-jenisVehicle").on("change", function () {
const vehicleSelected = $(this).val();
const box = $("#edit-jenis-member").empty().append('<option selected value="">Pilih Jenis</option>');
(member || []).forEach((item) => {
if (String(item.id_mobil) === String(vehicleSelected) && item.id_mobil !== 0) {
box.append(`<option value="${item.tarif}">${item.jenis_langganan} - ${item.tarif}</option>`);
}
});
});
$("#edit-nopol-add").on("click", function () {
const nopol = $(".enopol-add").val().trim();
const vehicle = $("#edit-jenisVehicle").val();
const vehicleName = $("#edit-jenisVehicle option:selected").text();
const memberTxt = $("#edit-jenis-member option:selected").text();
const tarif = parseFloat($("#edit-jenis-member").val()) || 0;
const merk = $(".emerk").val().trim();
$(".enopol-add").removeClass("is-invalid");
if (!vehicle) {
Swal.fire({icon: "warning", title: "Belum lengkap", text: "Wajib pilih jenis kendaraan"});
return;
}
$.ajax({
url: "/transaksi/stiker/checkField",
type: "POST",
data: { nopol: nopol },
headers: { "X-CSRF-token": $('meta[name="csrf-token"]').attr("content") },
success: function (rr) {
if (rr.response === true) {
$("#edit-list-nopol").append(`
<tr data-tarif="${tarif}">
<td class="text-center">${nopol}</td>
<td class="text-center" data-value="${vehicle}">${vehicleName}</td>
<td class="text-center">${memberTxt}</td>
<td class="text-center">${merk}</td>
<td class="text-center p-1"><button class="btn btn-danger btn-xs edit-del-nopol" type="button">Hapus</button></td>
</tr>`);
$(".enopol-add, .emerk").val("");
$("#edit-jenisVehicle, #edit-jenis-member").val("");
updateHargaUbah();
} else {
$(".enopol-add").addClass("is-invalid").next(".invalid-feedback").text(rr.message).show();
}
},
error: function (xhr) {
Swal.fire({icon: "error", title: "Gagal memeriksa", text: xhr.responseJSON?.message || "Terjadi kesalahan saat memeriksa nopol"});
},
});
});
$(".ekartu").on("keydown", function (e) {
if (e.key === "Enter") { e.preventDefault(); $(".enourut").focus(); }
});
$(".enourut").on("keydown", function (e) {
if (e.key !== "Enter") return;
e.preventDefault();
const nocard = $(".ekartu").val().trim();
const nourut = $(".enourut").val().trim();
if (!nocard) return;
$("#edit-list-card").append(`
<tr><td class="text-center">${nocard}</td><td class="text-center">${nourut}</td>
<td class="text-center p-2"><button class="btn btn-danger btn-xs edit-del-card" type="button">Hapus</button></td></tr>`);
$(".ekartu, .enourut").val("");
});
$("#edit-list-nopol").on("click", ".edit-del-nopol", function () {
$(this).closest("tr").remove();
updateHargaUbah();
});
$("#edit-list-card").on("click", ".edit-del-card", function () {
$(this).closest("tr").remove();
});
$(".simpan-ubah").on("click", function () {
const btn = $(this);
const notrans = $("#edit-notrans").val();
if (!notrans) {
Swal.fire({icon: "warning", title: "Belum ada data", text: "Buka ulang modal Ubah dari baris transaksi."});
return;
}
const nopolData = [];
$("#edit-list-nopol tr").each(function () {
const row = $(this);
nopolData.push({
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(),
});
});
const cardNumber = [];
$("#edit-list-card tr").each(function () {
const row = $(this);
const nocard = row.find("td:nth-child(1)").text().trim();
if (!nocard) return;
cardNumber.push({ nocard: nocard, nourut: row.find("td:nth-child(2)").text().trim() });
});
const payload = {
nama: $("#ubah-member [name='nama']").val(),
address: $("#ubah-member [name='address']").val(),
phone: $("#ubah-member [name='phone']").val(),
member: $("#ubah-member [name='member']").val(),
depart: $("#ubah-member [name='depart']").val(),
note: $("#ubah-member [name='note']").val(),
harga: $("#ubah-member [name='harga']").val(),
daterange: $("#ubah-member [name='daterange']").val(),
nopolData: nopolData,
cardNumber: cardNumber,
};
const saveHtml = btn.html();
btn.prop("disabled", true).html('<span class="spinner-border spinner-border-sm me-1"></span>Menyimpan...');
$.ajax({
url: `${location.origin}/transaksi/member/${encodeURIComponent(notrans)}`,
type: "PUT",
data: payload,
headers: { "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content") },
success: function (r) {
$("#ubah-member").modal("hide");
Swal.fire({icon: "success", title: "Berhasil", text: r.message || "Data diperbarui."}).then(() => location.reload());
},
error: function (xhr) {
Swal.fire({icon: "error", title: "Gagal menyimpan", text: xhr.responseJSON?.message || "Periksa kembali isian."});
},
complete: function () {
btn.prop("disabled", false).html(saveHtml);
},
});
});
});
+11 -1
View File
@@ -247,7 +247,7 @@ $(document).ready(function () {
if (r.response === true) {
harga += Number(memberExtValue);
var rowhtml = `
<tr>
<tr data-baru="1">
<td class="text-center">${nopolExt}</td>
<td class="text-center" data-value="${vehicleExt}">${vehicleExtName}</td>
<td class="text-center">${memberExt}</td>
@@ -295,6 +295,7 @@ $(document).ready(function () {
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(),
_baru: row.data("baru") ? 1 : 0,
};
nopolData.push(rowData);
});
@@ -343,6 +344,15 @@ $(document).ready(function () {
console.log(extData);
// Notrans lama (mengandung "/") -> perpanjang di tempat, nomor dipertahankan.
// Notrans baru -> alur lama (bikin nomor baru via member.store).
if (String(formDataExt.notrans || "").includes("/")) {
url = "/transaksi/stiker/extend-old";
extData = Object.assign({ notrans: formDataExt.notrans }, formDataExt, {
nopolData: nopolData,
});
}
$.ajax({
url: url,
type: "POST",
+196 -1
View File
@@ -286,7 +286,202 @@ $(document).ready(function () {
}).then(function () {
// Reload halaman setelah SweetAlert ditutup
location.reload(); // Ini akan me-reload halaman
});
// ================= UBAH (update di tempat, nomor dipertahankan) =================
function updateHargaUbah() {
let total = 0;
$("#ubah-member #edit-list-nopol tr").each(function () {
total += parseFloat($(this).data("tarif")) || 0;
});
$("#ubah-member input[name='harga']").val(total);
}
function showUrlFor(notrans) {
const tpl = $("#btnExtend").data("url") || "/transaksi/stiker/:id";
return tpl.replace(":id", encodeURIComponent(notrans));
}
$(document).on("click", ".edit-button", function () {
const notrans = (typeof selectedRowData !== "undefined" && selectedRowData?.notrans)
|| $(this).data("id") || "";
if (!notrans) {
Swal.fire({icon: "warning", title: "Belum pilih data", text: "Klik salah satu baris transaksi dulu."});
return;
}
$.ajax({
url: showUrlFor(notrans),
type: "GET",
success: function (r) {
const m = $("#ubah-member");
m.find("#edit-notrans").val(r.notrans);
m.find("[name='nama']").val(r.nama ?? "");
m.find("[name='address']").val(r.alamat ?? "");
m.find("[name='phone']").val(r.telepon ?? "");
m.find("[name='note']").val(r.keterangan ?? "");
m.find("[name='harga']").val(r.harga ?? 0);
m.find("[name='member']").val(r.jenis_langganan ?? "");
m.find("[name='depart']").val(r.unit_kerja ?? "");
if (r.awal && r.akhir) {
m.find("[name='daterange']").val(
`${moment(r.awal).format("DD-MM-YYYY HH:mm")} - ${moment(r.akhir).format("DD-MM-YYYY HH:mm")}`,
);
}
const jl = $("#edit-list-nopol").empty();
(r.stiker_details || []).forEach((row) => {
jl.append(`
<tr data-tarif="${row.tarif ?? 0}">
<td class="text-center">${row.nopol ?? ""}</td>
<td class="text-center" data-value="${row.jenis_mobil ?? ""}">${row.vehicle ?? ""}</td>
<td class="text-center">${row.jenis_member ?? ""}</td>
<td class="text-center">${row.merk ?? ""}</td>
<td class="text-center p-1"><button class="btn btn-danger btn-xs edit-del-nopol" type="button">Hapus</button></td>
</tr>`);
});
const kl = $("#edit-list-card").empty();
(r.memberCard || []).forEach((ro) => {
kl.append(`
<tr>
<td class="text-center">${ro.no_card ?? ""}</td>
<td class="text-center">${ro.no_urut ?? ""}</td>
<td class="text-center p-2"><button class="btn btn-danger btn-xs edit-del-card" type="button">Hapus</button></td>
</tr>`);
});
updateHargaUbah();
},
error: function (xhr) {
Swal.fire({icon: "error", title: "Gagal memuat data", text: xhr.responseJSON?.message || "Data tidak ditemukan."});
},
});
});
$("#edit-jenisVehicle").on("change", function () {
const vehicleSelected = $(this).val();
const box = $("#edit-jenis-member").empty().append('<option selected value="">Pilih Jenis</option>');
(member || []).forEach((item) => {
if (String(item.id_mobil) === String(vehicleSelected) && item.id_mobil !== 0) {
box.append(`<option value="${item.tarif}">${item.jenis_langganan} - ${item.tarif}</option>`);
}
});
});
$("#edit-nopol-add").on("click", function () {
const nopol = $(".enopol-add").val().trim();
const vehicle = $("#edit-jenisVehicle").val();
const vehicleName = $("#edit-jenisVehicle option:selected").text();
const memberTxt = $("#edit-jenis-member option:selected").text();
const tarif = parseFloat($("#edit-jenis-member").val()) || 0;
const merk = $(".emerk").val().trim();
$(".enopol-add").removeClass("is-invalid");
if (!vehicle) {
Swal.fire({icon: "warning", title: "Belum lengkap", text: "Wajib pilih jenis kendaraan"});
return;
}
$.ajax({
url: "/transaksi/stiker/checkField",
type: "POST",
data: { nopol: nopol },
headers: { "X-CSRF-token": $('meta[name="csrf-token"]').attr("content") },
success: function (rr) {
if (rr.response === true) {
$("#edit-list-nopol").append(`
<tr data-tarif="${tarif}">
<td class="text-center">${nopol}</td>
<td class="text-center" data-value="${vehicle}">${vehicleName}</td>
<td class="text-center">${memberTxt}</td>
<td class="text-center">${merk}</td>
<td class="text-center p-1"><button class="btn btn-danger btn-xs edit-del-nopol" type="button">Hapus</button></td>
</tr>`);
$(".enopol-add, .emerk").val("");
$("#edit-jenisVehicle, #edit-jenis-member").val("");
updateHargaUbah();
} else {
$(".enopol-add").addClass("is-invalid").next(".invalid-feedback").text(rr.message).show();
}
},
error: function (xhr) {
Swal.fire({icon: "error", title: "Gagal memeriksa", text: xhr.responseJSON?.message || "Terjadi kesalahan saat memeriksa nopol"});
},
});
});
$(".ekartu").on("keydown", function (e) {
if (e.key === "Enter") { e.preventDefault(); $(".enourut").focus(); }
});
$(".enourut").on("keydown", function (e) {
if (e.key !== "Enter") return;
e.preventDefault();
const nocard = $(".ekartu").val().trim();
const nourut = $(".enourut").val().trim();
if (!nocard) return;
$("#edit-list-card").append(`
<tr><td class="text-center">${nocard}</td><td class="text-center">${nourut}</td>
<td class="text-center p-2"><button class="btn btn-danger btn-xs edit-del-card" type="button">Hapus</button></td></tr>`);
$(".ekartu, .enourut").val("");
});
$("#edit-list-nopol").on("click", ".edit-del-nopol", function () {
$(this).closest("tr").remove();
updateHargaUbah();
});
$("#edit-list-card").on("click", ".edit-del-card", function () {
$(this).closest("tr").remove();
});
$(".simpan-ubah").on("click", function () {
const btn = $(this);
const notrans = $("#edit-notrans").val();
if (!notrans) {
Swal.fire({icon: "warning", title: "Belum ada data", text: "Buka ulang modal Ubah dari baris transaksi."});
return;
}
const nopolData = [];
$("#edit-list-nopol tr").each(function () {
const row = $(this);
nopolData.push({
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(),
});
});
const cardNumber = [];
$("#edit-list-card tr").each(function () {
const row = $(this);
const nocard = row.find("td:nth-child(1)").text().trim();
if (!nocard) return;
cardNumber.push({ nocard: nocard, nourut: row.find("td:nth-child(2)").text().trim() });
});
const payload = {
nama: $("#ubah-member [name='nama']").val(),
address: $("#ubah-member [name='address']").val(),
phone: $("#ubah-member [name='phone']").val(),
member: $("#ubah-member [name='member']").val(),
depart: $("#ubah-member [name='depart']").val(),
note: $("#ubah-member [name='note']").val(),
harga: $("#ubah-member [name='harga']").val(),
daterange: $("#ubah-member [name='daterange']").val(),
nopolData: nopolData,
cardNumber: cardNumber,
};
const saveHtml = btn.html();
btn.prop("disabled", true).html('<span class="spinner-border spinner-border-sm me-1"></span>Menyimpan...');
$.ajax({
url: `${location.origin}/transaksi/member/${encodeURIComponent(notrans)}`,
type: "PUT",
data: payload,
headers: { "X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr("content") },
success: function (r) {
$("#ubah-member").modal("hide");
Swal.fire({icon: "success", title: "Berhasil", text: r.message || "Data diperbarui."}).then(() => location.reload());
},
error: function (xhr) {
Swal.fire({icon: "error", title: "Gagal menyimpan", text: xhr.responseJSON?.message || "Periksa kembali isian."});
},
complete: function () {
btn.prop("disabled", false).html(saveHtml);
},
});
});
});
} else if (r.status == false) {
Swal.fire({icon: "error", title: "Oops...", text: r.message || "Terjadi kendala, silakan hubungi admin."});
}
File diff suppressed because one or more lines are too long
@@ -64,6 +64,7 @@
<li class="nav-item"><a class="nav-link" href="/report/laporan-per-pintu">Teransaksi Per Pintu</a></li>
<li class="nav-item"><a class="nav-link" href="/report/laporan-semua-transaksi">Transaksi Harian</a></li>
<li class="nav-item"><a class="nav-link" href="/report/stiker/harian">Member Harian</a></li>
<li class="nav-item"><a class="nav-link" href="/report/statistik">Statistik Transaksi</a></li>
</ul>
</div>
</li>
+200
View File
@@ -0,0 +1,200 @@
@extends('layouts.master')
@section('styles')
<style>
#tabel-statistik th[data-info] { cursor: default; }
.stat-card { border-radius: 10px; }
</style>
@endsection
@section('content')
<div class="container-fluid page-body-wrapper">
<div class="main-panel">
<div class="content-wrapper">
<div class="row">
<div class="col-md-12 grid-margin stretch-card">
<div class="card">
<div class="card-body">
<h4 class="card-title">Statistik Transaksi</h4>
<small class="text-muted d-block mb-3">Periode berdasarkan tanggal masuk. Pecah per hari / minggu / bulan.</small>
<form id="filter-statistik" action="javascript:void(0)">
<div class="row align-items-end g-2">
<div class="col-md-2">
<label class="form-label" for="grup">Pecah Per</label>
<select class="form-select form-select-sm" id="grup">
<option value="hari" selected>Hari</option>
<option value="minggu">Minggu</option>
<option value="bulan">Bulan</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label" for="tgl-awal">Periode Awal</label>
<input type="date" class="form-control form-control-sm" id="tgl-awal">
</div>
<div class="col-md-2">
<label class="form-label" for="tgl-akhir">Periode Akhir</label>
<input type="date" class="form-control form-control-sm" id="tgl-akhir">
</div>
<div class="col-md-2">
<label class="form-label" for="status-transaksi">Status Transaksi</label>
<select class="form-select form-select-sm" id="status-transaksi">
<option value="" selected>Semua</option>
<option value="0">Casual</option>
<option value="3">Member</option>
<option value="32">Denda</option>
<option value="-1">Masalah</option>
</select>
</div>
<div class="col-md-2">
<label class="form-label" for="cara">Pembayaran</label>
<select class="form-select form-select-sm" id="cara">
<option value="" selected>Semua</option>
<option value="tunai">Tunai</option>
<option value="cashless">Cashless</option>
</select>
</div>
<div class="col-md-2 d-flex gap-2">
<button type="submit" class="btn btn-sm btn-primary" id="btn-proses">Proses</button>
</div>
</div>
</form>
<hr class="my-3">
<div class="row g-2 mb-3" id="ringkasan">
<div class="col-md-2"><div class="border rounded p-2 h-100 stat-card"><small class="text-muted">Transaksi</small><div class="fw-bold" id="sum-trx">-</div></div></div>
<div class="col-md-2"><div class="border rounded p-2 h-100 stat-card"><small class="text-muted">Keluar</small><div class="fw-bold" id="sum-keluar">-</div></div></div>
<div class="col-md-2"><div class="border rounded p-2 h-100 stat-card"><small class="text-muted">Pendapatan</small><div class="fw-bold text-success" id="sum-pendapatan">-</div></div></div>
<div class="col-md-3"><div class="border rounded p-2 h-100 stat-card"><small class="text-muted">Tunai</small><div class="fw-bold" id="sum-tunai">-</div></div></div>
<div class="col-md-3"><div class="border rounded p-2 h-100 stat-card"><small class="text-muted">Cashless</small><div class="fw-bold text-info" id="sum-cashless">-</div></div></div>
</div>
<div class="mb-3" style="max-height: 320px;"><canvas id="chart-statistik" height="120"></canvas></div>
<div class="table-responsive">
<table class="table table-bordered table-striped" id="tabel-statistik">
<thead>
<tr>
<th>Periode</th>
<th class="text-end">Transaksi</th>
<th class="text-end">Keluar</th>
<th class="text-end">Pendapatan</th>
<th class="text-end">Tunai</th>
<th class="text-end">Cashless</th>
</tr>
</thead>
<tbody id="body-statistik">
<tr><td colspan="6" class="text-center text-muted">Pilih filter lalu klik Proses.</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('javascript')
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script src="{{ asset('vendor/sweetalert2/dist/sweetalert2.min.js') }}"></script>
<script src="{{ asset('vendors/chart.js/Chart.min.js') }}"></script>
<script>
$(function () {
const dataUrl = '{{ route('statistik.data', [], false) }}';
let chart = null;
const today = new Date().toISOString().split('T')[0];
const awal = new Date(Date.now() - 29 * 864e5).toISOString().split('T')[0];
$('#tgl-akhir').val(today);
$('#tgl-awal').val(awal);
function fmtRp(v) {
return 'Rp ' + Number(v || 0).toLocaleString('id-ID');
}
function fmtN(v) {
return Number(v || 0).toLocaleString('id-ID');
}
function labelPeriode(p, grup) {
if (grup === 'bulan') {
const d = new Date(p + 'T00:00:00');
return d.toLocaleDateString('id-ID', { month: 'short', year: 'numeric' });
}
if (grup === 'minggu') return 'Minggu ' + p;
const d = new Date(p + 'T00:00:00');
return d.toLocaleDateString('id-ID', { day: '2-digit', month: 'short' });
}
function drawChart(rows, grup) {
const ctx = document.getElementById('chart-statistik');
if (chart) chart.destroy();
chart = new Chart(ctx, {
type: 'bar',
data: {
labels: rows.map(r => labelPeriode(r.periode, grup)),
datasets: [
{ label: 'Tunai', data: rows.map(r => Number(r.tunai)), backgroundColor: '#198754' },
{ label: 'Cashless', data: rows.map(r => Number(r.cashless)), backgroundColor: '#0dcaf0' },
],
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: { y: { beginAtZero: true, ticks: { callback: v => (v / 1000) + 'rb' } } },
},
});
}
$('#filter-statistik').on('submit', function () {
const btn = $('#btn-proses'); const btnHtml = btn.html();
btn.prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1"></span>Memproses...');
$('#body-statistik').html('<tr><td colspan="6" class="text-center text-muted py-4"><span class="spinner-border spinner-border-sm me-2"></span>Memuat statistik...</td></tr>');
const grup = $('#grup').val();
$.ajax({
url: dataUrl,
type: 'GET',
data: {
grup: grup,
tgl_awal: $('#tgl-awal').val(),
tgl_akhir: $('#tgl-akhir').val(),
status_transaksi: $('#status-transaksi').val(),
cara: $('#cara').val(),
},
success: function (r) {
const s = r.ringkasan || {};
$('#sum-trx').text(fmtN(s.transaksi) + ' trx');
$('#sum-keluar').text(fmtN(s.keluar) + ' trx');
$('#sum-pendapatan').text(fmtRp(s.pendapatan));
$('#sum-tunai').text(fmtRp(s.tunai));
$('#sum-cashless').text(fmtRp(s.cashless));
if (!r.data.length) {
$('#body-statistik').html('<tr><td colspan="6" class="text-center">Data tidak ditemukan.</td></tr>');
if (chart) chart.destroy();
return;
}
let html = '';
r.data.forEach(function (row) {
html += '<tr><td>' + labelPeriode(row.periode, grup) + '</td>'
+ '<td class="text-end">' + fmtN(row.transaksi) + '</td>'
+ '<td class="text-end">' + fmtN(row.keluar) + '</td>'
+ '<td class="text-end">' + fmtRp(row.pendapatan) + '</td>'
+ '<td class="text-end">' + fmtRp(row.tunai) + '</td>'
+ '<td class="text-end">' + fmtRp(row.cashless) + '</td></tr>';
});
$('#body-statistik').html(html);
drawChart(r.data, grup);
},
error: function (xhr) {
let msg = 'Gagal memuat statistik.';
if (xhr.responseJSON?.message) msg = xhr.responseJSON.message;
else if (xhr.status === 422 && xhr.responseJSON?.errors) msg = Object.values(xhr.responseJSON.errors).flat().join(' ');
$('#body-statistik').html('<tr><td colspan="6" class="text-center text-danger">' + msg + '</td></tr>');
Swal.fire({icon: 'error', title: 'Gagal memuat', text: msg});
},
complete: function () {
btn.prop('disabled', false).html(btnHtml);
},
});
});
$('#filter-statistik').submit();
});
</script>
@endsection
+13 -12
View File
@@ -22,6 +22,7 @@
<form class="ubah-member" id="edit-member" methode="POST">
@csrf
@method('PUT')
<input type="hidden" name="notrans" id="edit-notrans">
<div class="tab-content">
<div class="tab-pane fade show active" id="edit-data" role="tabpanel">
<div class="pt-4">
@@ -63,7 +64,7 @@
<div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label">Jenis</label>
<div class="col-lg-6">
<select class="select-data form-control" name="member" id="member">
<select class="select-data form-control" name="member" id="edit-member-sel">
<option selected value="">Pilih Jenis</option>
@foreach ($memberSelect as $member)
<option value="{{ $member->id }}">{{ $member->nama }}</option>
@@ -74,7 +75,7 @@
<div class="mb-3 row">
<label class="col-lg-3 col-form-label form-label">Dept</label>
<div class="col-lg-6">
<select class="select-data form-control" name="depart" id="department">
<select class="select-data form-control" name="depart" id="edit-department">
<option selected value="">Pilih Departemen</option>
@foreach ($departmentSelect as $department)
<option value="{{ $department->kode }}">{{ $department->nama }}</option>
@@ -104,12 +105,12 @@
<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 enopol-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="select form-control" id="edit-jenisVehicle">
<option selected value="">Jenis Kendaraan</option>
@foreach ($vehicleSelect as $kendaraan)
<option value="{{ $kendaraan->id }}">{{ $kendaraan->nama }}</option>
@@ -118,16 +119,16 @@
</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="select-data form-control member" id="edit-jenis-member">
</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 emerk" 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="edit-nopol-add" class="btn btn-primary btn-xs">Tambah</button>
</div>
<hr>
<table class="table-bordered table-striped">
@@ -140,7 +141,7 @@
<th class="col-2 text-center">Aksi</th>
</tr>
</thead>
<tbody id="list-nopol">
<tbody id="edit-list-nopol">
</tbody>
</table>
</div>
@@ -152,11 +153,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 ekartu" 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 enourut" placeholder="01234567...">
</div>
<hr>
{{-- <div class="table-responsive"> --}}
@@ -168,7 +169,7 @@
<th class="col-2 text-center">Aksi</th>
</tr>
</thead>
<tbody id="list-card">
<tbody id="edit-list-card">
</tbody>
</table>
{{-- </div> --}}
@@ -181,7 +182,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-ubah">Simpan Perubahan</button>
</div>
</div>
</div>
+3
View File
@@ -105,6 +105,7 @@
Route::post("stiker/data", [MemberStikerController::class, "getMember"])->name("getMember");
// ->where(["stiker"] => '.*');
Route::post("stiker/checkField", [MemberStikerController::class, "checkField"]);
Route::post("stiker/extend-old", [MemberStikerController::class, "extendOld"])->name("stiker.extendOld");
Route::resource("member", MemberStikerController::class);
});
@@ -118,6 +119,8 @@
);
Route::get("/laporan-per-pintu", [ReportTransaksiController::class, "byGate"]);
Route::get("/laporan-per-pintu/data", [ReportTransaksiController::class, "gateData"])->name("gateData");
Route::get("/statistik", [ReportTransaksiController::class, "statistik"])->name("statistik");
Route::get("/statistik/data", [ReportTransaksiController::class, "statistikData"])->name("statistik.data");
Route::prefix("stiker")->group(function () {
Route::get("/harian", [LaporanStiker::class, "laporanStikerHarian"]);