update vehicles history maintenance

This commit is contained in:
wayanrivan
2026-07-13 11:52:15 +07:00
parent a0ce09cdda
commit 583ea3b9a0
4 changed files with 365 additions and 11 deletions

View File

@ -0,0 +1,102 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Http\Response;
use App\Responses;
use App\Models\UserLogs;
use Validator;
class MaintenanceHistoryController extends Controller
{
/**
* API Get data history maintenance
*/
public function api_get_data_maintenance($vehicleid)
{
try {
$data = DB::table('t_vehicles_maintenance_history as h')
->leftJoin('t_maintenance_vhc_type as t', function ($join) {
$join->whereRaw('FIND_IN_SET(t.id, h.idservice)');
})
->select(
'h.*',
DB::raw('GROUP_CONCAT(t.service_type SEPARATOR ", ") as service_names')
)
->where('h.vhc_id', $vehicleid)
->groupBy('h.id')
->get();
$apiResp = Responses::success("success list maintenance vehicles");
$apiResp["data"] = $data;
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
$apiResp = Responses::error($e->getMessage());
return new Response($apiResp, $apiResp["meta"]["code"]);
}
}
/**
* API Update data history maintenance
*/
public function api_update_data_maintenance(Request $req, $vehicleid, $id)
{
try {
$now = time();
$input = [
"idservice" => $req->idservice,
"odometer" => $req->odometer,
"dates" => $req->dates,
"ismaintenance"=> $req->ismaintenance
];
$rulesInput = [
"idservice" => "required|string",
"odometer" => "required|numeric",
"dates" => "required|string",
"ismaintenance" => "required|numeric"
];
// validasi input
$isValidInput = Validator::make($input, $rulesInput);
if (!$isValidInput->passes()) {
$apiResp = Responses::bad_input($isValidInput->messages()->first());
return new Response($apiResp, $apiResp["meta"]["code"]);
}
DB::beginTransaction();
$update = [
"idservice" => $req->idservice,
"vhc_id" => $vehicleid,
"odometer" => $req->odometer,
"dates" => $req->dates,
"ismaintenance" => $req->ismaintenance,
// "created_at" => date("Y-m-d H:i:s", $now),
// "udt" => date("Y-m-d H:i:s", $now),
// "created_by" => Auth::user()->id,
// "uby" => Auth::user()->id
];
DB::table('t_vehicles_maintenance_history')->where('id', $id)->update($update);
$apiResp = Responses::created("success update maintenance vehicles");
DB::commit();
$log = [
"module" => "Vehicle Type",
"action" => "Create",
"desc" => "Add new vehicle maintenance type: " . $req->service_type,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
DB::rollBack();
$apiResp = Responses::error($e->getMessage());
return new Response($apiResp, $apiResp["meta"]["code"]);
}
}
}

View File

@ -72,7 +72,7 @@ class VehiclesController extends Controller
$filter["company"] = $req->cptid;
}
$list = DB::select("SELECT
v.id, v.name, v.device_id, nopol1, nopol2, nopol3, v.sum_milleage,
v.id, v.name, v.device_id, nopol1, nopol2, nopol3, v.sum_milleage,
fvhc_img, vyear, t.`desc` type_name, c_name company_name, rltm.crt_d, rltm.crt crt_rltm,
dvc.crt, dvc.simcard, fuel_curr, dvc.protocol, v.cat_id, vc.name cat_name
FROM t_vehicles as v
@ -84,7 +84,7 @@ class VehiclesController extends Controller
left join t_gps_tracks_rltm rltm on rltm.vhc_id = v.id
LEFT JOIN t_vehicles_cats AS vc ON v.cat_id = vc.id
WHERE v.dlt is null
ORDER BY v.id ASC
ORDER BY v.id ASC
");
foreach ($list as $key => $row) {

View File

@ -679,6 +679,41 @@
</div>
</div>
</div>
<div class="modal fade" data-bs-backdrop="static" data-bs-keyboard="false" id="mdlHistoryMaintenance"
aria-labelledby="mdlHistoryMaintenanceLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="mdlHistoryMaintenanceLabel">Maintenance History</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="table-responsive">
<table class="table table-bordered table-sm align-middle" id="tHistoryMaintenance">
<thead>
<tr>
<th style="width:35%">Service Type</th>
<th style="width:15%">Odometer</th>
<th style="width:15%">Date</th>
<th style="width:15%" class="text-center">Maintenance?</th>
<th style="width:20%" class="text-center">Action</th>
</tr>
</thead>
<tbody id="tHistoryMaintenanceBody">
<tr>
<td colspan="4" class="text-center">No data</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
@endsection
@section('customjs')
@ -705,6 +740,7 @@
VNew.activate();
VEdt.activate();
VDel.activate();
VHistory.activate();
Filter.activate();
// images
DUploadAdd.activate('fvhc');
@ -805,14 +841,14 @@
searchable: true,
render: function (data, type, row, meta) {
let action = `
<a href="#" class="text-decoration-none me-1 btnEdtVhc" data-vid="${data}">
<span class="icon ion-eye fz-16"></span>
</a>
`;
// <a href="#" class="text-decoration-none text-danger btnDelVhc">
// <span class="icon ion-trash-b fz-16"></span>
// </a>
return action;
<a href="#" class="text-decoration-none me-1 btnEdtVhc" data-vid="${data}">
<span class="icon ion-eye fz-16"></span>
</a>
<a href="#" class="text-decoration-none me-1 btnHistoryVhc" data-vid="${data}" data-catid="${row.cat_id}" title="Maintenance History">
<span class="icon ion-clipboard fz-16"></span>
</a>
`;
return action;
}
},
{
@ -2003,6 +2039,219 @@
},
}
const VHistory = {
options_service: [],
activate: function () {
this.event();
},
event: function () {
// buka modal
$('#tVehicles').on('click', '.btnHistoryVhc', function (e) {
e.preventDefault();
const vid = $(this).data('vid');
const catid = $(this).data('catid');
$('#mdlHistoryMaintenance').data('vid', vid);
VHistory.load(vid, catid);
$('#mdlHistoryMaintenance').modal('show');
});
// toggle Edit -> enable row
$('#tHistoryMaintenanceBody').on('click', '.btnEditRow', function () {
const $row = $(this).closest('tr');
VHistory.enableRow($row);
$(this)
.text('Save')
.removeClass('btn-outline-primary btnEditRow')
.addClass('btn-success btnSaveRow');
});
// Save row
$('#tHistoryMaintenanceBody').on('click', '.btnSaveRow', function () {
const $row = $(this).closest('tr');
VHistory.saveRow($row, $(this));
});
},
load: async function (vid, catid) {
$('#tHistoryMaintenanceBody').html('<tr><td colspan="5" class="text-center">Loading...</td></tr>');
try {
const respTypes = await VHistory.loadServiceTypes(catid);
VHistory.options_service = respTypes || [];
$.ajax({
url: "{{ route('api_get_data_maintenance', '') }}/" + vid,
method: 'GET',
headers: { 'x-api-key': Helper.getCookie('_trtk') },
success: (res) => {
if (res.meta.type != 'success') {
Helper.toast('Warning', 'just now', res.meta.message);
$('#tHistoryMaintenanceBody').html('<tr><td colspan="5" class="text-center">Failed to load data</td></tr>');
return;
}
VHistory.render(res.data);
},
error: (jqXHR) => {
$('#tHistoryMaintenanceBody').html('<tr><td colspan="5" class="text-center">Failed to load data</td></tr>');
Helper.toast('Error', 'just now', jqXHR.responseJSON?.meta?.message || 'Failed to load history');
},
});
} catch (e) {
$('#tHistoryMaintenanceBody').html('<tr><td colspan="5" class="text-center">Failed to load data</td></tr>');
}
},
loadServiceTypes: function (catid) {
return new Promise((resolve) => {
$.ajax({
url: "{{ route('api_list_vehicle_maintenance_type') }}",
method: 'GET',
headers: { 'x-api-key': Helper.getCookie('_trtk') },
success: (res) => resolve(res.data || []),
error: () => resolve([]),
});
});
},
render: function (rows) {
if (!rows || rows.length < 1) {
$('#tHistoryMaintenanceBody').html('<tr><td colspan="5" class="text-center">No data</td></tr>');
return;
}
let html = '';
rows.forEach((row) => {
const selectedIds = (row.idservice || '').split(',').map((v) => v.trim());
const options = VHistory.options_service
.map((opt) => `<option value="${opt.id}" ${selectedIds.includes(String(opt.id)) ? 'selected' : ''}>${opt.service_type}</option>`)
.join('');
const isChecked = Number(row.ismaintenance) === 1 ? 'checked' : '';
html += `
<tr data-hid="${row.id}">
<td>
<select class="form-select form-select-sm history-service" multiple disabled style="width:100%;">
${options}
</select>
</td>
<td>
<input type="number" class="form-control form-control-sm history-odometer" value="${row.odometer ?? ''}" disabled>
</td>
<td>
<input type="text" class="form-control form-control-sm history-date" value="${row.dates ?? ''}" disabled autocomplete="off">
</td>
<td class="text-center">
<div class="form-check form-switch d-flex justify-content-center">
<input class="form-check-input history-ismaintenance" type="checkbox" role="switch" ${isChecked} disabled>
</div>
</td>
<td class="text-center">
<button type="button" class="btn btn-sm btn-outline-primary btnEditRow">Edit</button>
</td>
</tr>
`;
});
$('#tHistoryMaintenanceBody').html(html);
// init select2 tiap row (tetap disabled dulu)
$('#tHistoryMaintenanceBody .history-service').each(function () {
$(this).select2({
dropdownParent: $('#mdlHistoryMaintenance'),
width: '100%',
});
});
// init datepicker tiap row
$('#tHistoryMaintenanceBody .history-date').datepicker({
format: 'yyyy-mm-dd',
});
},
enableRow: function ($row) {
$row.find('.history-odometer').prop('disabled', false);
$row.find('.history-date').prop('disabled', false);
$row.find('.history-ismaintenance').prop('disabled', false);
const $select = $row.find('.history-service');
$select.prop('disabled', false);
$select.select2('destroy');
$select.select2({
dropdownParent: $('#mdlHistoryMaintenance'),
width: '100%',
});
},
disableRow: function ($row) {
$row.find('.history-odometer').prop('disabled', true);
$row.find('.history-date').prop('disabled', true);
$row.find('.history-ismaintenance').prop('disabled', true);
const $select = $row.find('.history-service');
$select.prop('disabled', true);
$select.select2('destroy');
$select.select2({
dropdownParent: $('#mdlHistoryMaintenance'),
width: '100%',
});
},
saveRow: function ($row, $btn) {
const vid = $('#mdlHistoryMaintenance').data('vid');
const hid = $row.data('hid');
const idservice = $row.find('.history-service').val(); // array
const odometer = $row.find('.history-odometer').val();
const dates = $row.find('.history-date').val();
const ismaintenance = $row.find('.history-ismaintenance').is(':checked') ? 1 : 0;
if (!idservice || idservice.length < 1) {
Helper.toast('Validasi', 'just now', 'Service type wajib diisi');
return;
}
if (!odometer) {
Helper.toast('Validasi', 'just now', 'Odometer wajib diisi');
return;
}
if (!dates) {
Helper.toast('Validasi', 'just now', 'Tanggal wajib diisi');
return;
}
$btn.prop('disabled', true).text('Saving...');
$.ajax({
url: "{{ route('api_update_data_maintenance', ['vehicleid' => '__VID__', 'id' => '__ID__']) }}"
.replace('__VID__', vid)
.replace('__ID__', hid),
method: 'PUT',
headers: {
'x-api-key': Helper.getCookie('_trtk'),
'x-csrf-token': $('meta[name="csrf-token"]').attr('content'),
},
data: {
idservice: idservice.join(','),
odometer: odometer,
dates: dates,
ismaintenance: ismaintenance,
},
success: (res) => {
$btn.prop('disabled', false);
if (res.meta.type != 'success') {
Helper.toast('Warning', 'just now', res.meta.message);
$btn.text('Save');
return;
}
Helper.toast('Success', 'just now', 'Berhasil update maintenance history');
VHistory.disableRow($row);
$btn.text('Edit')
.removeClass('btn-success btnSaveRow')
.addClass('btn-outline-primary btnEditRow');
},
error: (jqXHR) => {
$btn.prop('disabled', false).text('Save');
if (jqXHR.status >= 500) {
Helper.toast('Error', 'just now', 'Please try again');
} else {
Helper.toast('Error', 'just now', jqXHR.responseJSON?.meta?.message || 'Gagal menyimpan');
}
},
});
},
};
Wrapper.activate();
</script>
@endsection
@endsection

View File

@ -334,6 +334,9 @@ Route::middleware(["auth", "auth.user"])->group(function () {
"api_search_nopol"
);
Route::get("/api/maintenances/{vid}", "MaintenanceHistoryController@api_get_data_maintenance")->name("api_get_data_maintenance");
Route::put("/api/maintenances/{vehicleid}/{id}", "MaintenanceHistoryController@api_update_data_maintenance")->name("api_update_data_maintenance");
Route::get("/api/zones", "ZoneController@api_list_zones")->name("api_list_zones");
Route::get("/api/zones/{zid}", "ZoneController@api_show_zone")->name("api_show_zone");
Route::post("/api/zones", "ZoneController@api_add_zone")->name("api_add_zone");