Compare commits

..

42 Commits

Author SHA1 Message Date
8b833e7295 update user logs 2025-10-21 03:18:05 +07:00
0b79bd665c update logs 2025-10-20 15:41:18 +07:00
81efe25ce2 user logs update 2025-10-20 15:36:39 +07:00
fbbcf86509 user logs 2025-10-16 11:59:06 +07:00
18897186e0 update trip report 2025-10-08 16:01:55 +07:00
136db1a158 time fix 2025-10-08 12:15:14 +07:00
f4202ce146 api_report_vehicle_trips_list 2025-09-29 14:36:03 +07:00
a2c821d4bd device vehicle 2025-09-26 15:32:01 +07:00
1331e4a46e tgl filter 1 2025-09-25 19:22:42 +07:00
111c3e35a1 tgl filter 2025-09-25 19:12:36 +07:00
4b5cb88e4a tgl filter 2025-09-25 19:11:21 +07:00
0284d25af4 dashboard fix 2025-09-25 16:51:16 +07:00
a92d0c632f timefix 2025-09-24 13:35:28 +07:00
77e5b345b7 limit osrm request 2025-09-24 13:20:46 +07:00
a3f42315e4 disable filter 2025-09-23 11:18:03 +07:00
65f7cc1ebf query update 2025-09-22 15:24:26 +07:00
e1153c375d time fix 1 2025-09-18 14:48:15 +07:00
0fe1cac8bf timefix 2025-09-18 13:41:30 +07:00
0621b14b77 update bounds 2025-09-18 08:42:44 +07:00
08776e5a1b update 2025-09-17 09:41:20 +07:00
b3a2467629 dtl trip report 2025-09-17 07:07:56 +07:00
dae0954891 fitBounds 2025-09-14 23:09:42 +07:00
d013eb6dd1 enable default filter 2025-09-14 23:03:47 +07:00
8445a18416 timeout fix 2025-09-14 23:01:35 +07:00
d1f90af6f6 osrm 2025-09-11 14:20:51 +07:00
f6d11ce5e9 time fix 2025-09-11 12:49:41 +07:00
993b529331 query fix 2025-09-11 11:21:05 +07:00
5c687fc24f query fix 2025-09-11 11:00:34 +07:00
bc92ed5234 update & fix 2025-09-11 10:34:45 +07:00
5d585a6e26 detail trip 2025-09-11 10:11:42 +07:00
64235d08f5 hidden 2025-09-10 13:32:32 +07:00
85d29d4142 default tgl 2025-09-10 10:55:51 +07:00
620486de25 report, timezone 2025-09-10 10:53:10 +07:00
48fe5e3ef6 fix edit vehicle 2025-09-10 08:51:52 +07:00
e49309f709 label role 2025-09-09 13:13:47 +07:00
36ec099cd9 vehicle detail 2025-09-09 13:02:52 +07:00
c7e0b57ad6 pool 2025-09-09 12:37:15 +07:00
a101b55279 distribution category 2025-09-09 12:25:32 +07:00
c075457053 sms notif 2025-09-09 09:10:08 +07:00
0217c1c947 typo 2025-09-03 23:28:38 +07:00
ff5b411178 update report 2025-09-03 23:03:59 +07:00
14f5246c86 update button 2025-09-03 19:08:19 +07:00
35 changed files with 7151 additions and 303 deletions

1
.gitignore vendored
View File

@ -13,3 +13,4 @@ npm-debug.log
yarn-error.log
.vscode
.DS_Store
.sql

View File

@ -9,6 +9,7 @@ class Helper
const EARTH_RADIUS_M = 6371000;
const EARTH_RADIUS_KM = 6371;
const EARTH_RADIUS_MILES = 3959; // 3958.756 || 3959 || 3963
const TIMEFIX = 25200;
/**
* Calculates the great-circle distance between two points, with

View File

@ -13,6 +13,7 @@ use App\Models\Users;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\JsonResponse;
use Illuminate\Validation\ValidationException;
use App\Models\UserLogs;
class LoginController extends Controller
{
@ -143,6 +144,12 @@ class LoginController extends Controller
// return $req->wantsJson()
// ? new JsonResponse([], 204)
// : redirect(route('view_dashboard'));
$log = [
"module" => "Auth",
"action" => "Login",
"desc" => "User login",
];
UserLogs::insert(Auth::user()->id, $log);
if ($req->wantsJson()) {
return new JsonResponse([], 204);
@ -173,6 +180,13 @@ class LoginController extends Controller
public function logout(Request $req)
{
$user = Auth::user();
$log = [
"module" => "Auth",
"action" => "Logout",
"desc" => "User logout",
];
UserLogs::insert(Auth::user()->id, $log);
if ($user->role == Users::ROLE_ADMIN) {
$this->guard()->logout();
$req->session()->invalidate();

View File

@ -12,6 +12,8 @@ use App\Responses;
use App\Helper;
use App\Models\Clients;
use App\Models\Users;
use App\Models\UserLogs;
use Auth;
class ClientController extends Controller
{
@ -25,6 +27,13 @@ class ClientController extends Controller
$data = [
"disc_types" => Clients::select2DiscountTypes(),
];
$log = [
"module" => "Company",
"action" => "View",
"desc" => "Open Company Menu",
];
UserLogs::insert(Auth::user()->id, $log);
return view("menu_v1.clients", $data);
}
@ -250,6 +259,13 @@ class ClientController extends Controller
$apiResp = Responses::created("success add new client");
DB::commit();
$log = [
"module" => "Company",
"action" => "Create",
"desc" => "Add new company: ".$req->cname,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
Storage::disk("public")->delete($url_clogo);
@ -481,6 +497,13 @@ class ClientController extends Controller
DB::commit();
$apiResp = Responses::success("success update client");
$log = [
"module" => "Company",
"action" => "Update",
"desc" => "Update company: ".$req->cname,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
Storage::disk("public")->delete($url_clogo);
@ -541,6 +564,13 @@ class ClientController extends Controller
DB::commit();
$apiResp = Responses::success("success delete client");
$log = [
"module" => "Company",
"action" => "Delete",
"desc" => "Delete company: ".$client[0]->c_name,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
DB::rollBack();

View File

@ -0,0 +1,232 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Validator;
use Hidehalo\Nanoid\Client as Nanoid;
use Hidehalo\Nanoid\GeneratorInterface as NanoidInterface;
use App\Responses;
use App\Helper;
use App\Models\ConfRates;
use App\Models\ConfTruckTypes;
use App\Models\Vehicles;
use App\Models\UserLogs;
use Auth;
class ConfDistributionController extends Controller
{
/**
* View
*/
public function view_distribution_category(Request $req)
{
$data = [];
$log = [
"module" => "Distribution Category",
"action" => "View",
"desc" => "Open Distribution Category menu",
];
UserLogs::insert(Auth::user()->id, $log);
return view("menu_v1.configs.distribution_category", $data);
}
/**
* API
*/
public function api_add_distribution_category(Request $req)
{
try {
$now = time();
// new or edit
$tipe = $req->tipe ?? "new";
$input = [
"dc_code" => $req->dc_code,
"dc_name" => $req->dc_name,
];
$rulesInput = [
"dc_code" => "required|string|max:10",
"dc_name" => "required|string|max:100",
];
// validasi input
$isValidInput = Validator::make($input, $rulesInput);
if (!$isValidInput->passes()) {
$apiResp = Responses::bad_input($isValidInput->messages()->first());
return new Response($apiResp, $apiResp["meta"]["code"]);
}
if($tipe == "new"){
$uniqCode = DB::select("SELECT * FROM t_conf_distribution_category WHERE dc_code = ?", [$req->dc_code]);
}
if($tipe == "edit"){
$uniqCode = DB::select("SELECT * FROM t_conf_distribution_category WHERE dc_code = ? AND id != ?", [$req->dc_code, $req->id]);
}
if (count($uniqCode) > 0) {
$apiResp = Responses::bad_request("type code has been used");
return new Response($apiResp, $apiResp["meta"]["code"]);
}
DB::beginTransaction();
$insD = [
"dc_code" => $req->dc_code,
"dc_name" => $req->dc_name,
"crt" => $now,
"crt_by" => $req->auth->uid,
"updt" => $now,
"updt_by" => $req->auth->uid,
];
// $insQ = DB::insert("INSERT
// INTO t_conf_distribution_category (dc_code, dc_name, crt, crt_by, updt, updt_by)
// VALUES (?, ?, ?, ?, ?, ?)
// ", array_values($insD));
$insQ = DB::insert("INSERT into t_conf_distribution_category set
dc_code = ?,
dc_name = ?,
crt = ?,
crt_by = ?,
updt = ?,
updt_by = ?
on duplicate key update
dc_code = values(dc_code),
dc_name = values(dc_name),
updt = values(updt),
updt_by = values(updt_by)
", array_values($insD));
$apiResp = Responses::created("success " . ($tipe == "new" ? "add new" : "edit") . " distribution category");
DB::commit();
$log = [
"module" => "Distribution Category",
"action" => "".($tipe == "new" ? "Create" : "Update")."",
"desc" => "".($tipe == "new" ? "Add new" : "Update")." distribution category: ".$req->dc_name,
];
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"]);
}
}
public function api_list_distribution_category(Request $req)
{
try {
$now = time();
$list = DB::select("SELECT * FROM t_conf_distribution_category WHERE dlt IS NULL ORDER BY dc_code ASC");
foreach ($list as $key => $row) {
$list[$key]->DT_RowIndex = $key + 1;
$list[$key]->action = "-";
}
$apiResp = Responses::success("success list distribution category");
$apiResp["count"] = count($list);
$apiResp["data"] = $list;
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
$apiResp = Responses::error($e->getMessage());
return new Response($apiResp, $apiResp["meta"]["code"]);
}
}
public function api_show_distribution_category(Request $req, $id)
{
try {
$input = [
"id" => $id,
];
$rulesInput = [
"id" => "required|integer|not_in:0",
];
// validasi input
$isValidInput = Validator::make($input, $rulesInput);
if (!$isValidInput->passes()) {
$apiResp = Responses::bad_input($isValidInput->messages()->first());
return new Response($apiResp, $apiResp["meta"]["code"]);
}
$dtl = DB::select("SELECT * FROM t_conf_distribution_category WHERE id = ? AND dlt IS NULL limit 1", [$id]);
if (count($dtl) < 1) {
$apiResp = Responses::not_found("truck type not found");
return new Response($apiResp, $apiResp["meta"]["code"]);
}
$apiResp = Responses::success("success get detail vehicle type");
$apiResp["data"] = $dtl[0];
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
$apiResp = Responses::error($e->getMessage());
return new Response($apiResp, $apiResp["meta"]["code"]);
}
}
public function api_del_distribution_category(Request $req, $id)
{
try {
$now = time();
$input = [
"id" => $id,
];
$rulesInput = [
"id" => "required|integer|not_in:0",
];
// validasi input
$isValidInput = Validator::make($input, $rulesInput);
if (!$isValidInput->passes()) {
$apiResp = Responses::bad_input($isValidInput->messages()->first());
return new Response($apiResp, $apiResp["meta"]["code"]);
}
$dtl = DB::select("SELECT * FROM t_conf_distribution_category WHERE id = ? AND dlt IS NULL limit 1", [$id]);
if (count($dtl) < 1) {
$apiResp = Responses::not_found("vehicle type not found");
return new Response($apiResp, $apiResp["meta"]["code"]);
}
DB::beginTransaction();
$updtQ = DB::update("UPDATE t_conf_distribution_category
SET
dlt = ?,
dlt_by = ?,
updt = ?,
updt_by = ?
WHERE id = ? AND dlt IS NULL
", [1, $req->user()->id, $now, $req->user()->id, $id]);
DB::commit();
$apiResp = Responses::success("success delete vehicle type");
$log = [
"module" => "Distribution Category",
"action" => "Delete",
"desc" => "Delete distribution category: ".$dtl[0]->dc_name,
];
UserLogs::insert(Auth::user()->id, "Delete distribution category: ".$dtl[0]->dc_name);
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

@ -0,0 +1,233 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Validator;
use Hidehalo\Nanoid\Client as Nanoid;
use Hidehalo\Nanoid\GeneratorInterface as NanoidInterface;
use App\Responses;
use App\Helper;
use App\Models\ConfRates;
use App\Models\ConfTruckTypes;
use App\Models\Vehicles;
use App\Models\UserLogs;
use Auth;
class ConfPoolController extends Controller
{
/**
* View
*/
public function view_pool(Request $req)
{
$data = [];
$log = [
"module" => "Pool",
"action" => "View",
"desc" => "Open Pool menu",
];
UserLogs::insert(Auth::user()->id, $log);
return view("menu_v1.configs.pool", $data);
}
/**
* API
*/
public function api_add_pool(Request $req)
{
try {
$now = time();
// new or edit
$tipe = $req->tipe ?? "new";
$input = [
"pool_code" => $req->pool_code,
"pool_name" => $req->pool_name,
];
$rulesInput = [
"pool_code" => "required|string|max:10",
"pool_name" => "required|string|max:100",
];
// validasi input
$isValidInput = Validator::make($input, $rulesInput);
if (!$isValidInput->passes()) {
$apiResp = Responses::bad_input($isValidInput->messages()->first());
return new Response($apiResp, $apiResp["meta"]["code"]);
}
if($tipe == "new"){
$uniqCode = DB::select("SELECT * FROM t_conf_pool WHERE pool_code = ?", [$req->pool_code]);
}
if($tipe == "edit"){
$uniqCode = DB::select("SELECT * FROM t_conf_pool WHERE pool_code = ? AND id != ?", [$req->pool_code, $req->id]);
}
if (count($uniqCode) > 0) {
$apiResp = Responses::bad_request("type code has been used");
return new Response($apiResp, $apiResp["meta"]["code"]);
}
DB::beginTransaction();
$insD = [
"pool_code" => $req->pool_code,
"pool_name" => $req->pool_name,
"crt" => $now,
"crt_by" => $req->auth->uid,
"updt" => $now,
"updt_by" => $req->auth->uid,
];
// $insQ = DB::insert("INSERT
// INTO t_conf_pool (pool_code, pool_name, crt, crt_by, updt, updt_by)
// VALUES (?, ?, ?, ?, ?, ?)
// ", array_values($insD));
$insQ = DB::insert("INSERT into t_conf_pool set
pool_code = ?,
pool_name = ?,
crt = ?,
crt_by = ?,
updt = ?,
updt_by = ?
on duplicate key update
pool_code = values(pool_code),
pool_name = values(pool_name),
updt = values(updt),
updt_by = values(updt_by)
", array_values($insD));
$apiResp = Responses::created("success " . ($tipe == "new" ? "add new" : "edit") . " distribution category");
DB::commit();
$log = [
"module" => "Pool",
"action" => "".($tipe == "new" ? "Create" : "Update")."",
"desc" => "".($tipe == "new" ? "Add new" : "Update")." pool: ".$req->pool_name,
];
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"]);
}
}
public function api_list_pool(Request $req)
{
try {
$now = time();
$list = DB::select("SELECT * FROM t_conf_pool WHERE dlt IS NULL ORDER BY pool_code ASC");
foreach ($list as $key => $row) {
$list[$key]->DT_RowIndex = $key + 1;
$list[$key]->action = "-";
}
$apiResp = Responses::success("success list distribution category");
$apiResp["count"] = count($list);
$apiResp["data"] = $list;
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
$apiResp = Responses::error($e->getMessage());
return new Response($apiResp, $apiResp["meta"]["code"]);
}
}
public function api_show_pool(Request $req, $id)
{
try {
$input = [
"id" => $id,
];
$rulesInput = [
"id" => "required|integer|not_in:0",
];
// validasi input
$isValidInput = Validator::make($input, $rulesInput);
if (!$isValidInput->passes()) {
$apiResp = Responses::bad_input($isValidInput->messages()->first());
return new Response($apiResp, $apiResp["meta"]["code"]);
}
$dtl = DB::select("SELECT * FROM t_conf_pool WHERE id = ? AND dlt IS NULL limit 1", [$id]);
if (count($dtl) < 1) {
$apiResp = Responses::not_found("truck type not found");
return new Response($apiResp, $apiResp["meta"]["code"]);
}
$apiResp = Responses::success("success get detail vehicle type");
$apiResp["data"] = $dtl[0];
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
$apiResp = Responses::error($e->getMessage());
return new Response($apiResp, $apiResp["meta"]["code"]);
}
}
public function api_del_pool(Request $req, $id)
{
try {
$now = time();
$input = [
"id" => $id,
];
$rulesInput = [
"id" => "required|integer|not_in:0",
];
// validasi input
$isValidInput = Validator::make($input, $rulesInput);
if (!$isValidInput->passes()) {
$apiResp = Responses::bad_input($isValidInput->messages()->first());
return new Response($apiResp, $apiResp["meta"]["code"]);
}
$dtl = DB::select("SELECT * FROM t_conf_pool WHERE id = ? AND dlt IS NULL limit 1", [$id]);
if (count($dtl) < 1) {
$apiResp = Responses::not_found("vehicle type not found");
return new Response($apiResp, $apiResp["meta"]["code"]);
}
DB::beginTransaction();
$updtQ = DB::update("UPDATE t_conf_pool
SET
dlt = ?,
dlt_by = ?,
updt = ?,
updt_by = ?
WHERE id = ? AND dlt IS NULL
", [1, $req->user()->id, $now, $req->user()->id, $id]);
DB::commit();
$apiResp = Responses::success("success delete vehicle type");
$log = [
"module" => "Pool",
"action" => "Delete",
"desc" => "Delete pool: ".$dtl[0]->pool_name,
];
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

@ -15,6 +15,8 @@ use App\Helper;
use App\Models\ConfRates;
use App\Models\ConfTruckTypes;
use App\Models\Vehicles;
use App\Models\UserLogs;
use Auth;
class ConfTruckTypeController extends Controller
{
@ -26,6 +28,12 @@ class ConfTruckTypeController extends Controller
{
$data = [];
$log = [
"module" => "Vehicle Type",
"action" => "View",
"desc" => "Open Vehicle Type menu",
];
UserLogs::insert(Auth::user()->id, $log);
return view("menu_v1.configs.truck_types", $data);
}
@ -163,6 +171,14 @@ class ConfTruckTypeController extends Controller
$apiResp = Responses::created("success add new vehicle type");
DB::commit();
$log = [
"module" => "Vehicle Type",
"action" => "Create",
"desc" => "Add new vehicle type: ".$req->type_name,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
DB::rollBack();
@ -256,6 +272,13 @@ class ConfTruckTypeController extends Controller
$apiResp = Responses::created("success update vehicle type");
DB::commit();
$log = [
"module" => "Vehicle Type",
"action" => "Update",
"desc" => "Update vehicle type: ".$req->type_name,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
DB::rollBack();
@ -310,6 +333,13 @@ class ConfTruckTypeController extends Controller
DB::commit();
$apiResp = Responses::success("success delete vehicle type");
$log = [
"module" => "Vehicle Type",
"action" => "Delete",
"desc" => "Delete vehicle type: ".$truckType[0]->name,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
DB::rollBack();

View File

@ -11,6 +11,8 @@ use App\Responses;
use App\Helper;
use App\Models\Devices;
use App\Models\Vehicles;
use App\Models\UserLogs;
use Auth;
class DevicesController extends Controller
{
@ -20,6 +22,13 @@ class DevicesController extends Controller
$data = [
"vhcs" => $vhcs,
];
$log = [
"module" => "Device",
"action" => "View",
"desc" => "Open Device menu",
];
UserLogs::insert(Auth::user()->id, $log);
return view("menu_v1.configs.devices", $data);
}
@ -202,6 +211,13 @@ class DevicesController extends Controller
$apiResp = Responses::created("success add new device");
DB::commit();
$log = [
"module" => "Device",
"action" => "Create",
"desc" => "Add new device: ".$device_id,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
DB::rollBack();
@ -354,6 +370,13 @@ class DevicesController extends Controller
$apiResp = Responses::created("success update device");
DB::commit();
$log = [
"module" => "Device",
"action" => "Update",
"desc" => "Edit device: ".$device_id,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
DB::rollBack();
@ -403,6 +426,13 @@ class DevicesController extends Controller
DB::commit();
$apiResp = Responses::success("success delete device");
$log = [
"module" => "Device",
"action" => "Delete",
"desc" => "Delete device : ".$device[0]->device_id,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
DB::rollBack();

View File

@ -16,6 +16,7 @@ use App\Models\Drivers;
use App\Models\DriversDetail;
use App\Models\Users;
use App\Models\DrvPhoneDevices;
use App\Models\UserLogs;
class DriversController extends Controller
{
@ -252,6 +253,13 @@ class DriversController extends Controller
$apiResp = Responses::created("success add new driver");
DB::commit();
$log = [
"module" => "Driver",
"action" => "View",
"desc" => "Add new driver: ".$req->fullname,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
Storage::disk("public")->delete($url_ktp);
@ -451,6 +459,13 @@ class DriversController extends Controller
DB::commit();
$apiResp = Responses::success("success update driver");
$log = [
"module" => "Driver",
"action" => "Update",
"desc" => "Edit driver: ".$req->fullname,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
Storage::disk("public")->delete($url_ktp);
@ -501,6 +516,13 @@ class DriversController extends Controller
DB::commit();
$apiResp = Responses::success("success delete driver");
$log = [
"module" => "Driver",
"action" => "Delete",
"desc" => "Delete driver : ".$driver[0]->fullname,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
DB::rollBack();

View File

@ -30,11 +30,20 @@ use App\Models\Finance;
use App\Models\OrdersCheckpoints;
use App\Models\OrdersInvoices;
use App\Models\OrdersDriversUploads;
use App\Models\UserLogs;
use Auth;
class MenuController extends Controller
{
public function view_dashboard(Request $req)
{
$log = [
"module" => "Dashboard",
"action" => "View",
"desc" => "Open Dashboard menu",
];
UserLogs::insert($req->auth->uid, $log);
$data = [
"client_group" => Clients::getClientById($req->auth->client_group_id),
];
@ -45,11 +54,19 @@ class MenuController extends Controller
$data["client_group"] = null;
}
return view("menu_v1.dashboard", $data);
}
public function view_drivers(Request $req)
{
$log = [
"module" => "Driver",
"action" => "View",
"desc" => "Open Driver menu",
];
UserLogs::insert($req->auth->uid, $log);
$data = [
"bloods" => Helper::listBloods(),
"relationships" => Drivers::listRelationships(),
@ -61,6 +78,16 @@ class MenuController extends Controller
public function view_vehicles(Request $req)
{
$log = [
"module" => "Vehicle",
"action" => "View",
"desc" => "Open Vehicle menu",
];
UserLogs::insert($req->auth->uid, $log);
$listPool = DB::select("SELECT * FROM t_conf_pool WHERE dlt IS NULL ORDER BY pool_code ASC");
$listDistribution = DB::select("SELECT * FROM t_conf_distribution_category WHERE dlt IS NULL ORDER BY dc_code ASC");
$data = [
// 'cats' => Vehicles::listCats(), // default Truck
"brands" => Vehicles::listBrands(),
@ -73,6 +100,8 @@ class MenuController extends Controller
"is_idle_yes" => 1,
"is_available" => Devices::IS_AVAIL,
]),
"listPool" => $listPool,
"listDistribution" => $listDistribution
];
// dd($data);
return view("menu_v1.vehicles", $data);
@ -84,6 +113,13 @@ class MenuController extends Controller
public function view_transactions()
{
$log = [
"module" => "Transactions",
"action" => "View",
"desc" => "Open Transactions menu",
];
UserLogs::insert(Auth::user()->id, $log);
$data = [
"availOrdToMerge" => Finance::availOrdToMerge(),
];
@ -169,6 +205,14 @@ class MenuController extends Controller
}
public function view_transactions_view(Request $req)
{
$log = [
"module" => "Transactions",
"action" => "View",
"desc" => "Open Transactions menu",
];
UserLogs::insert($req->auth->uid, $log);
$codes = explode(",", $req->code);
$limit = count($codes);
if ($limit > 2) {
@ -330,6 +374,13 @@ class MenuController extends Controller
public function view_logs_gps()
{
$log = [
"module" => "Logs GPS",
"action" => "View",
"desc" => "Open Logs GPS menu",
];
UserLogs::insert(Auth::user()->id, $log);
return view("menu_v1.configs.index_logs_gps");
}

View File

@ -17,23 +17,31 @@ use Maatwebsite\Excel\Concerns\WithHeadings;
use Maatwebsite\Excel\Concerns\WithStyles;
use Maatwebsite\Excel\Concerns\WithCustomStartCell;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use Carbon\Carbon;
use App\Models\UserLogs;
class ReportsController extends Controller
{
public function view_report_vehicle_trips(Request $req)
{
$q = "select id, nopol1 from t_vehicles WHERE dlt is null order by nopol1";
$q = "SELECT id, nopol1 from t_vehicles WHERE dlt is null order by nopol1";
$listNopol = DB::select($q);
$data = [
'listNopol' => $listNopol,
];
$log = [
"module" => "Vehicle Trips Report",
"action" => "View",
"desc" => "Open Vehicle Trips Report menu",
];
UserLogs::insert(Auth::user()->id, $log);
return view('menu_v1.reports.vehicle_trips', $data);
}
public function api_report_vehicle_trips_list(Request $req)
{
// $TIMEFIX = Helper::TIMEFIX;
// Validate input
// date in unix datetime format
// dd($req->type);
@ -49,58 +57,106 @@ class ReportsController extends Controller
return new Response($apiResp, $apiResp["meta"]["code"]);
}
$from_date = $req->input('from_date');
$to_date = $req->input('to_date');
// $from_date = $req->input('from_date');
// $to_date = $req->input('to_date');
$from_date = $req->input('from_date') - Helper::TIMEFIX;
$to_date = $req->input('to_date') - Helper::TIMEFIX;
$vid = $req->input('vid');
// $from_date = 1756054800;
// $to_date = 1756745940;
// get month year
$date = Carbon::createFromTimestamp($from_date);
$yymm = $date->format('ym');
try {
$q = "
WITH trips AS (
// $list = DB::select("WITH
// gaps AS (
// SELECT
// -- previous gap since previous row > 1 hour (3600s)
// CASE
// WHEN (crt_d - LAG(crt_d, 1, NULL) OVER (PARTITION BY vhc_id ORDER BY crt_d)) > 3600
// THEN 1 ELSE 0
// END AS isStop,
// t.*
// FROM tracks_2509 t
// WHERE
// t.latitude IS NOT NULL
// AND t.longitude IS NOT NULL
// AND t.action = 'location'
// AND t.crt_d BETWEEN ? AND ?
// )
// , trips AS (
// SELECT
// -- mark the start of a trip when ignition=4 and previous ignition <> 4
// CASE
// WHEN ignition = 4
// AND LAG(ignition, 1, 0) OVER (PARTITION BY vhc_id ORDER BY crt_d) <> 4
// or LAG(isStop, 1, 0) over (PARTITION BY vhc_id ORDER BY crt_d) = 1
// THEN 1 ELSE 0
// END AS trip_start,
// g.*
// FROM gaps g
// )
// , numbered AS (
// SELECT
// *,
// -- assign a trip_id by cumulative sum of trip_start
// SUM(trip_start) OVER (PARTITION BY vhc_id ORDER BY crt_d) AS trip_id
// FROM trips
// where
// ignition = 4
// and isStop = 0
// ),
// agg AS (
// SELECT
// COUNT(*) AS row_count,
// v.name,
// v.nopol1,
// vhc_id,
// -- trip_id,
// ROW_NUMBER() OVER (PARTITION BY v.id ORDER BY MIN(a.crt_d)) AS trip_id,
// SUM(pre_milleage) AS mileage,
// MIN(a.crt_d) AS start,
// MAX(a.crt_d) AS finish,
// MIN(a.vhc_milleage) AS startMileage,
// MAX(a.vhc_milleage) AS finishMileage,
// (SELECT fulladdress FROM t_gps_tracks_address WHERE master_id = MIN(a.id) LIMIT 1) AS startLoc,
// (SELECT fulladdress FROM t_gps_tracks_address WHERE master_id = MAX(a.id) LIMIT 1) AS finishLoc
// FROM t_vehicles v
// LEFT JOIN numbered a ON a.vhc_id = v.id
// WHERE
// v.dlt is null and trip_id != 0
// and if(? , v.id = ? , 1=1)
// GROUP BY v.id, a.trip_id
// HAVING COUNT(*) > 1
// )
// SELECT
// *,
// SUM(mileage) OVER (PARTITION BY agg.id) AS total_mileage,
// COUNT(trip_id) OVER (PARTITION BY agg.id) AS total_trip,
// tvd.pool_code, tvd.dc_code
// FROM agg agg
// join t_vehicles_detail tvd on tvd.vid = agg.vhc_id
// ORDER BY agg.id, trip_id
// ", [$from_date, $to_date, $vid, $vid]);
$list = DB::select("WITH TotalMileage AS (
SELECT id, SUM(mileage) AS total_mileage, count(*) total_trip
FROM trips
WHERE start BETWEEN ? AND ?
GROUP BY id
)
SELECT
t.*,
-- mark the start of a trip when ignition=4 and previous ignition <> 4
CASE
WHEN ignition = 4
AND LAG(ignition, 1, 0) OVER (PARTITION BY vhc_id ORDER BY crt_d) <> 4
THEN 1 ELSE 0
END AS trip_start
FROM t_gps_tracks t
tm.total_mileage, total_trip,
ROW_NUMBER() OVER (PARTITION BY t.id ORDER BY t.start) AS trip_id
FROM trips t
JOIN TotalMileage tm ON t.id = tm.id
WHERE
t.latitude IS NOT NULL
AND t.longitude IS NOT NULL
AND t.action = 'location'
AND t.crt_d BETWEEN ? AND ?
)
, numbered AS (
SELECT
*,
-- assign a trip_id by cumulative sum of trip_start
SUM(trip_start) OVER (PARTITION BY vhc_id ORDER BY crt_d) AS trip_id
FROM trips
)
select
v.id,
-- coalesce(max(a.trip_id), 0) numOfTrip,
-- SUM(pre_milleage) AS total_milleage,
v.name, v.nopol1,
vhc_id, trip_id,
sum(pre_milleage) milleage, min(a.crt_d ) start, max(a.crt_d ) finish,
(select fulladdress from t_gps_tracks_address where master_id = min(a.id) limit 1) startLoc,
(select fulladdress from t_gps_tracks_address where master_id = max(a.id) limit 1) finishLoc
FROM
t_vehicles v
left join numbered a on a.vhc_id = v.id
WHERE
v.dlt is null and trip_id != 0
and if(? , v.id = ? , 1=1)
GROUP BY v.id, a.trip_id
ORDER BY v.id
";
$d = [$from_date, $to_date, $vid, $vid];
$list = DB::select($q, $d);
t.start BETWEEN ? AND ?
and if(? , t.id = ? , 1=1)
", [$from_date, $to_date, $from_date, $to_date, $vid, $vid]);
// // RETURN 1 - LIST
// if($req->type != 'report'){
@ -181,6 +237,64 @@ class ReportsController extends Controller
// return Responses::json(Responses::SERVER_ERROR, 'An error occurred while generating the report.', (object)[]);
}
}
public function view_report_trip_detail(Request $req)
{
$vid = $req->vid;
$tgl0 = $req->tgl0;
$tgl1 = $req->tgl1;
$nopol1 = $req->nopol1;
$d = [$vid, $tgl0, $tgl1];
$list = DB::select("SELECT
t.crt_d , t.latitude, t.longitude, t.speed,
tgta.fulladdress,
t.pre_milleage, t.vhc_milleage
FROM
t_gps_tracks t
left join t_gps_tracks_address tgta on tgta.master_id = t.id
WHERE
t.vhc_id = ?
and t.latitude IS NOT NULL
AND t.longitude IS NOT NULL
AND t.action = 'location'
AND t.crt_d BETWEEN ? AND ?
ORDER BY t.crt_d asc
", $d);
$start = [
'time' => $list[0]->crt_d,
'fulladdress' => urldecode($list[0]->fulladdress),
'mileage' => $list[0]->vhc_milleage,
];
$finish = [
'time' => $list[count($list) - 1]->crt_d,
'fulladdress' => urldecode($list[count($list) - 1]->fulladdress),
'mileage' => $list[count($list) - 1]->vhc_milleage,
];
$t0 = Carbon::createFromTimestamp($list[0]->crt_d);
$t1 = Carbon::createFromTimestamp($list[count($list) - 1]->crt_d);
$diff = $t1->diff($t0);
$hours = $diff->h + ($diff->days * 24); // include days converted to hours
$minutes = $diff->i;
$duration = "{$hours} hour" . ($hours > 1 ? 's' : '') . " {$minutes} minute" . ($minutes > 1 ? 's' : '');
$distance = $list[count($list) - 1]->vhc_milleage - $list[0]->vhc_milleage;
$data = [
'nopol1' => $nopol1,
'vid' => $vid,
'tgl0' => $tgl0,
'tgl1' => $tgl1,
'list' => $list,
'start' => $start,
'finish' => $finish,
'duration' => $duration,
'distance' => $distance,
];
// dd($list);
return view('menu_v1.reports._trip_detail', $data);
}
public function view_report_abnormalities(Request $req)
{
@ -191,6 +305,12 @@ class ReportsController extends Controller
'listNopol' => $listNopol,
];
$log = [
"module" => "Abnormalities Report",
"action" => "View",
"desc" => "Open Abnormalities Report menu",
];
UserLogs::insert(Auth::user()->id, $log);
return view('menu_v1.reports.abnormalities', $data);
}
public function api_report_abnormalities_list(Request $req)
@ -208,16 +328,15 @@ class ReportsController extends Controller
return new Response($apiResp, $apiResp["meta"]["code"]);
}
$from_date = $req->input('from_date');
$to_date = $req->input('to_date');
$from_date = $req->input('from_date') - Helper::TIMEFIX;
$to_date = $req->input('to_date') - Helper::TIMEFIX;
$vid = $req->input('vid');
try {
$q = "
select
$list = DB::select("SELECT
tv.name, tv.nopol1,
t.crt_d, t.speed, tgta.fulladdress,
tvd.speed_limit
tvd.speed_limit, tvd.pool_code, tvd.dc_code
from
t_gps_tracks t
left join t_vehicles tv on tv.id = t.vhc_id
@ -229,12 +348,9 @@ class ReportsController extends Controller
AND t.crt_d BETWEEN ? AND ?
and if(? , tv.id = ? , 1=1)
-- and t.speed > tvd.speed_limit
having t.speed > tvd.speed_limit
having t.speed >= tvd.speed_limit
ORDER BY t.crt_d
";
$d = [$from_date, $to_date, $vid, $vid];
$list = DB::select($q, $d);
", [$from_date, $to_date, $vid, $vid]);
// // RETURN 1 - LIST
// if($req->type != 'report'){

View File

@ -15,6 +15,8 @@ use App\Models\Vehicles;
use App\Models\Banks;
use App\Models\UsersMenuPermissions;
use Spatie\Permission\PermissionRegistrar;
use Illuminate\Support\Facades\Auth;
use App\Models\UserLogs;
class RolesController extends Controller
{
@ -34,6 +36,13 @@ class RolesController extends Controller
}),
];
$log = [
"module" => "Role",
"action" => "View",
"desc" => "Open Role menu",
];
UserLogs::insert(Auth::user()->id, $log);
return view("menu_v1.roles", $data);
}
@ -118,6 +127,14 @@ class RolesController extends Controller
app()[PermissionRegistrar::class]->forgetCachedPermissions();
$apiResp = Responses::created("success add new role");
$log = [
"module" => "Role",
"action" => "Create",
"desc" => "Add new role : ".$req->name,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
DB::rollBack();
@ -156,6 +173,13 @@ class RolesController extends Controller
app()[PermissionRegistrar::class]->forgetCachedPermissions();
$apiResp = Responses::created("success update role");
$log = [
"module" => "Role",
"action" => "Update",
"desc" => "Update role : ".$req->name,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
DB::rollBack();
@ -198,6 +222,13 @@ class RolesController extends Controller
app()[PermissionRegistrar::class]->forgetCachedPermissions();
$apiResp = Responses::created("success delete role");
$log = [
"module" => "Role",
"action" => "Delete",
"desc" => "Delete role : ".$role[0]->name,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
DB::rollBack();

View File

@ -0,0 +1,71 @@
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Validator;
use Hidehalo\Nanoid\Client as Nanoid;
use Hidehalo\Nanoid\GeneratorInterface as NanoidInterface;
use App\Responses;
use App\Helper;
use App\Models\ConfRates;
use App\Models\ConfTruckTypes;
use App\Models\Vehicles;
use App\Models\UserLogs;
use Auth;
use App\Models\Users;
class UserLogsController extends Controller
{
/**
* View
*/
public function view_user_logs(Request $req)
{
$users = Users::listUsers();
$data = [
"users" => $users,
];
$log = [
"module" => "User Logs",
"action" => "View",
"desc" => "Open User Logs menu",
];
UserLogs::insert(Auth::user()->id, $log);
return view("menu_v1.userLogs", $data);
}
public function api_user_logs(Request $req)
{
try {
$tgl0 = $req->tgl0;
$tgl1 = $req->tgl1;
$userId = $req->userId;
$d = [$tgl0, $tgl1, $userId, $userId];
$list = DB::select("SELECT
a.*, b.email
FROM t_user_log a
join t_users b on a.userId = b.id
WHERE
a.crt BETWEEN ? AND ?
and if(? , a.userId = ? , 1=1)
order by a.crt desc
", $d);
$apiResp = Responses::success("success user logs");
$apiResp["data"] = $list;
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

@ -15,6 +15,8 @@ use App\Models\Clients;
use App\Models\Vehicles;
use App\Models\Banks;
use App\Models\UsersMenuPermissions;
use Illuminate\Support\Facades\Auth;
use App\Models\UserLogs;
class UsersController extends Controller
{
@ -50,6 +52,13 @@ class UsersController extends Controller
$data["clients"] = Clients::select2Client($req->auth->client_group_id);
}
$log = [
"module" => "User",
"action" => "View",
"desc" => "Open User menu",
];
UserLogs::insert(Auth::user()->id, $log);
return view("menu_v1.users", $data);
}
@ -173,6 +182,7 @@ class UsersController extends Controller
"bank_acc_number" => $req->bank_acc_number,
"bank_acc_name" => $req->bank_acc_name,
"status" => $req->status,
"status_sms" => $req->status_sms,
"is_tracking" => $req->is_tracking,
"vehicles" => $req->vehicles,
"is_vdr_bcng" => $req->is_vdr_bcng,
@ -195,6 +205,7 @@ class UsersController extends Controller
"bank_acc_number" => "nullable|numeric",
"bank_acc_name" => "nullable|string|max:255",
"status" => "required|integer|not_in:0",
"status_sms" => "required|integer",
"is_tracking" => "nullable|numeric",
"vehicles" => "nullable|array",
"is_vdr_bcng" => "nullable|numeric",
@ -294,6 +305,7 @@ class UsersController extends Controller
"crt_by" => $req->auth->uid,
"updt" => $now,
"updt_by" => $req->auth->uid,
"status_sms" => $req->status_sms,
];
if ($req->roles == Users::ROLE_CHECKER) {
// $data['chk_type'] = $req->chk_type;
@ -325,6 +337,14 @@ class UsersController extends Controller
$apiResp = Responses::created("success add new user");
DB::commit();
$log = [
"module" => "User",
"action" => "Create",
"desc" => "Add new user : ".$req->email,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
DB::rollBack();
@ -352,6 +372,7 @@ class UsersController extends Controller
"bank_acc_number" => $req->bank_acc_number,
"bank_acc_name" => $req->bank_acc_name,
"is_vdr_bcng" => $req->is_vdr_bcng,
"status_sms" => $req->status_sms,
];
$rulesInput = [
"uid" => "required|integer|not_in:0",
@ -365,6 +386,7 @@ class UsersController extends Controller
"bank_acc_number" => "nullable|numeric",
"bank_acc_name" => "nullable|string|max:255",
"is_vdr_bcng" => "nullable|numeric",
"status_sms" => "required|integer",
];
$data = [
"id" => $req->uid,
@ -440,6 +462,7 @@ class UsersController extends Controller
return new Response($apiResp, $apiResp["meta"]["code"]);
}
}
$data["status_sms"] = $req->status_sms;
// validasi input
$isValidInput = Validator::make($input, $rulesInput);
@ -523,6 +546,14 @@ class UsersController extends Controller
$apiResp = Responses::created("success update user");
DB::commit();
$log = [
"module" => "User",
"action" => "Update",
"desc" => "Update user : ".$req->email,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
DB::rollBack();
@ -704,6 +735,14 @@ class UsersController extends Controller
$apiResp = Responses::created("success delete user");
DB::commit();
$log = [
"module" => "User",
"action" => "Delete",
"desc" => "Delete user : ".$getUser[0]->email,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
DB::rollBack();
@ -770,4 +809,38 @@ class UsersController extends Controller
return new Response($apiResp, $apiResp["meta"]["code"]);
}
}
public function view_user_logs1(Request $req)
{
$id = $req->id;
$user = Users::getUserById($id)[0];
$data = [
'user' => $user
];
// dd($dtl);
return view('menu_v1._userLogs', $data);
}
public function api_user_logs1(Request $req)
{
try {
$id = $req->id;
$tgl0 = $req->tgl0;
$tgl1 = $req->tgl1;
$d = [$id, $tgl0, $tgl1];
$list = DB::select("SELECT * FROM t_user_log
WHERE userId = ? AND crt BETWEEN ? AND ?
order by crt desc
", $d);
$apiResp = Responses::success("success user logs");
$apiResp["data"] = $list;
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

@ -15,6 +15,7 @@ use App\Models\Vehicles;
use App\Models\Devices;
use App\Models\VehiclesDetail;
use App\Models\Users;
use App\Models\UserLogs;
class VehiclesController extends Controller
{
@ -152,6 +153,8 @@ class VehiclesController extends Controller
"tax_exp" => $req->tax_exp,
// "kir_exp" => $req->kir_exp,
// "vendor_id" => $req->vendor_id,
"dc_code" => $req->dc_code,
"pool_code" => $req->pool_code,
];
$rulesInput = [
"front_vehicle_photo" => "required|string",
@ -184,6 +187,8 @@ class VehiclesController extends Controller
"tax_exp" => "required|date_format:Y-m-d",
// "kir_exp" => "required|date_format:Y-m-d",
// "vendor_id" => "nullable|integer|not_in:0",
"dc_code" => "nullable|string",
"pool_code" => "nullable|string",
];
if ($req->auth->role == Users::ROLE_VENDOR) {
@ -249,6 +254,7 @@ class VehiclesController extends Controller
"crt_by" => $req->auth->uid,
"updt" => $now,
"updt_by" => $req->auth->uid,
];
// dd($insVhc);
if ($req->model_id) {
@ -316,12 +322,23 @@ class VehiclesController extends Controller
"regis_year" => $req->regis_year,
"tax_exp" => $req->tax_exp,
"kir_exp" => $req->kir_exp,
"dc_code" => $req->dc_code,
"pool_code" => $req->pool_code,
];
VehiclesDetail::addDetail($insDetail);
$apiResp = Responses::created("success add new vehicle");
DB::commit();
$log = [
"module" => "Vehicle",
"action" => "Create",
"desc" => "Add new vehicle: ".$req->vhc_name,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
Storage::disk("public")->delete($url_fvhc);
@ -375,6 +392,8 @@ class VehiclesController extends Controller
"tax_exp" => $req->tax_exp,
// "kir_exp" => $req->kir_exp,
// "vendor_id" => $req->vendor_id,
"dc_code" => $req->dc_code,
"pool_code" => $req->pool_code,
];
$rulesInput = [
"vid" => "required|integer|not_in:0",
@ -409,6 +428,8 @@ class VehiclesController extends Controller
"tax_exp" => "required|date_format:Y-m-d",
// "kir_exp" => "required|date_format:Y-m-d",
// "vendor_id" => "nullable|integer|not_in:0",
"dc_code" => "nullable|string",
"pool_code" => "nullable|string",
];
if ($req->auth->role == Users::ROLE_VENDOR) {
@ -563,6 +584,8 @@ class VehiclesController extends Controller
"regis_year" => $req->regis_year,
"tax_exp" => $req->tax_exp,
"kir_exp" => $req->kir_exp,
"dc_code" => $req->dc_code,
"pool_code" => $req->pool_code,
];
if ($req->fvhc_base64) {
$updtDetail["fvhc_img"] = $url_fvhc;
@ -575,6 +598,13 @@ class VehiclesController extends Controller
DB::commit();
$apiResp = Responses::success("success update vehicle");
$log = [
"module" => "Vehicle",
"action" => "Update",
"desc" => "Update vehicle: ".$req->vhc_name,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
Storage::disk("public")->delete($url_fvhc);
@ -632,6 +662,13 @@ class VehiclesController extends Controller
DB::commit();
$apiResp = Responses::success("success delete vehicle");
$log = [
"module" => "Vehicle",
"action" => "Delete",
"desc" => "Delete vehicle: ".$vehicle[0]->name,
];
UserLogs::insert(Auth::user()->id, $log);
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
DB::rollBack();

View File

@ -17,6 +17,8 @@ use App\Models\Orders;
use App\Models\OrdersDrops;
// use App\Models\OrdersPckDrop;
use App\Models\OrdersAItems;
use App\Models\UserLogs;
class ZoneController extends Controller
{
@ -25,8 +27,14 @@ class ZoneController extends Controller
*
* @return \Illuminate\Http\Response
*/
public function view_zone()
public function view_zone(Request $req)
{
$log = [
"module" => "Zone",
"action" => "View",
"desc" => "Open Zone menu",
];
UserLogs::insert($req->auth->uid, $log);
return view("menu_v1.zone");
}
@ -355,6 +363,13 @@ class ZoneController extends Controller
$apiResp = Responses::created("success add new zone");
$log = [
"module" => "Zone",
"action" => "Create",
"desc" => "Add Zone ".$req->zone_name,
];
UserLogs::insert($req->auth->uid, $log);
DB::commit();
return new Response($apiResp, $apiResp["meta"]["code"]);
} catch (\Exception $e) {
@ -481,6 +496,13 @@ class ZoneController extends Controller
Zone::updateZone($zid, $updtZone);
$log = [
"module" => "Zone",
"action" => "Update",
"desc" => "Update Zone ".$req->zone_name,
];
UserLogs::insert($req->auth->uid, $log);
DB::commit();
$apiResp = Responses::success("success update zone");
@ -524,6 +546,13 @@ class ZoneController extends Controller
"dlt_by" => $req->auth->uid,
]);
$log = [
"module" => "Zone",
"action" => "Delete ",
"desc" => "Delete Zone ".$zone[0]->name,
];
UserLogs::insert($req->auth->uid, $log);
DB::commit();
$apiResp = Responses::success("success delete zone");

View File

@ -386,10 +386,10 @@ class Tracks extends Model
$query .= " AND v.nopol1 = ? AND v.nopol2 = ? AND v.nopol3 = ?";
array_push($params, $filter["nopol1"], $filter["nopol2"], $filter["nopol3"]);
}
if (isset($filter["vid"])) {
$query .= " AND v.id = ?";
$params[] = $filter["vid"];
}
// if (isset($filter["vid"])) {
// $query .= " AND v.id = ?";
// $params[] = $filter["vid"];
// }
if (isset($filter["vids"])) {
if ($filter["vids"] && count($filter["vids"]) > 0) {
$placeholders = rtrim(str_repeat("?,", count($filter["vids"])), ",");
@ -433,7 +433,7 @@ class Tracks extends Model
$params[] = $filter["company"];
}
$query .= " GROUP BY v.id ORDER BY tr.crt_d DESC LIMIT 500";
$query .= " GROUP BY v.id ORDER BY tr.crt_s DESC LIMIT 500";
$list = DB::select($query, $params);
// dd($list);
@ -543,7 +543,9 @@ class Tracks extends Model
if (isset($filter["start_date"]) && isset($filter["end_date"])) {
$query .= " AND tr.crt_d BETWEEN ? AND ?";
array_push($params, $filter["start_date"], $filter["end_date"]);
$tgl0 = $filter["start_date"] - Helper::TIMEFIX;
$tgl1 = $filter["end_date"] - Helper::TIMEFIX;
array_push($params, $tgl0, $tgl1);
}
if (isset($filter["start_at"])) {

20
app/Models/UserLogs.php Normal file
View File

@ -0,0 +1,20 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
class UserLogs extends Model
{
public static function insert($userId, $log)
{
$now = time();
return DB::insert("INSERT into t_user_log
set
userId = ?,
log = ?,
crt = ?
", [$userId, json_encode($log), $now]);
}
}

350
public/assets/vendor/printThis.js vendored Normal file
View File

@ -0,0 +1,350 @@
/*
* printThis v2.0.0
* @desc Printing plug-in for jQuery
* @author Jason Day
* @author Samuel Rouse
*
* Resources (based on):
* - jPrintArea: http://plugins.jquery.com/project/jPrintArea
* - jqPrint: https://github.com/permanenttourist/jquery.jqprint
* - Ben Nadal: http://www.bennadel.com/blog/1591-Ask-Ben-Print-Part-Of-A-Web-Page-With-jQuery.htm
*
* Licensed under the MIT licence:
* http://www.opensource.org/licenses/mit-license.php
*
* (c) Jason Day 2015-2022
*
* Usage:
*
* $("#mySelector").printThis({
* debug: false, // show the iframe for debugging
* importCSS: true, // import parent page css
* importStyle: true, // import style tags
* printContainer: true, // grab outer container as well as the contents of the selector
* loadCSS: "path/to/my.css", // path to additional css file - use an array [] for multiple
* pageTitle: "", // add title to print page
* removeInline: false, // remove all inline styles from print elements
* removeInlineSelector: "body *", // custom selectors to filter inline styles. removeInline must be true
* printDelay: 1000, // variable print delay
* header: null, // prefix to html
* footer: null, // postfix to html
* base: false, // preserve the BASE tag, or accept a string for the URL
* formValues: true, // preserve input/form values
* canvas: true, // copy canvas elements
* doctypeString: '...', // enter a different doctype for older markup
* removeScripts: false, // remove script tags from print content
* copyTagClasses: true // copy classes from the html & body tag
* copyTagStyles: true, // copy styles from html & body tag (for CSS Variables)
* beforePrintEvent: null, // callback function for printEvent in iframe
* beforePrint: null, // function called before iframe is filled
* afterPrint: null // function called before iframe is removed
* });
*
* Notes:
* - the loadCSS will load additional CSS (with or without @media print) into the iframe, adjusting layout
*/
;
(function($) {
function appendContent($el, content) {
if (!content) return;
// Simple test for a jQuery element
$el.append(content.jquery ? content.clone() : content);
}
function appendBody($body, $element, opt) {
// Clone for safety and convenience
// Calls clone(withDataAndEvents = true) to copy form values.
var $content = $element.clone(opt.formValues);
if (opt.formValues) {
// Copy original select and textarea values to their cloned counterpart
// Makes up for inability to clone select and textarea values with clone(true)
copyValues($element, $content, 'select, textarea');
}
if (opt.removeScripts) {
$content.find('script').remove();
}
if (opt.printContainer) {
// grab $.selector as container
$content.appendTo($body);
} else {
// otherwise just print interior elements of container
$content.each(function() {
$(this).children().appendTo($body)
});
}
}
// Copies values from origin to clone for passed in elementSelector
function copyValues(origin, clone, elementSelector) {
var $originalElements = origin.find(elementSelector);
clone.find(elementSelector).each(function(index, item) {
$(item).val($originalElements.eq(index).val());
});
}
var opt;
$.fn.printThis = function(options) {
opt = $.extend({}, $.fn.printThis.defaults, options);
var $element = this instanceof jQuery ? this : $(this);
var strFrameName = "printThis-" + (new Date()).getTime();
if (window.location.hostname !== document.domain && navigator.userAgent.match(/msie/i)) {
// Ugly IE hacks due to IE not inheriting document.domain from parent
// checks if document.domain is set by comparing the host name against document.domain
var iframeSrc = "javascript:document.write(\"<head><script>document.domain=\\\"" + document.domain + "\\\";</s" + "cript></head><body></body>\")";
var printI = document.createElement('iframe');
printI.name = "printIframe";
printI.id = strFrameName;
printI.className = "MSIE";
document.body.appendChild(printI);
printI.src = iframeSrc;
} else {
// other browsers inherit document.domain, and IE works if document.domain is not explicitly set
var $frame = $("<iframe id='" + strFrameName + "' name='printIframe' />");
$frame.appendTo("body");
}
var $iframe = $("#" + strFrameName);
// show frame if in debug mode
if (!opt.debug) $iframe.css({
position: "absolute",
width: "0px",
height: "0px",
left: "-600px",
top: "-600px"
});
// before print callback
if (typeof opt.beforePrint === "function") {
opt.beforePrint();
}
// $iframe.ready() and $iframe.load were inconsistent between browsers
setTimeout(function() {
// Add doctype to fix the style difference between printing and render
function setDocType($iframe, doctype){
var win, doc;
win = $iframe.get(0);
win = win.contentWindow || win.contentDocument || win;
doc = win.document || win.contentDocument || win;
doc.open();
doc.write(doctype);
doc.close();
}
if (opt.doctypeString){
setDocType($iframe, opt.doctypeString);
}
var $doc = $iframe.contents(),
$head = $doc.find("head"),
$body = $doc.find("body"),
$base = $('base'),
baseURL;
// add base tag to ensure elements use the parent domain
if (opt.base === true && $base.length > 0) {
// take the base tag from the original page
baseURL = $base.attr('href');
} else if (typeof opt.base === 'string') {
// An exact base string is provided
baseURL = opt.base;
} else {
// Use the page URL as the base
baseURL = document.location.protocol + '//' + document.location.host;
}
$head.append('<base href="' + baseURL + '">');
// import page stylesheets
if (opt.importCSS) $("link[rel=stylesheet]").each(function() {
var href = $(this).attr("href");
if (href) {
var media = $(this).attr("media") || "all";
$head.append("<link type='text/css' rel='stylesheet' href='" + href + "' media='" + media + "'>");
}
});
// import style tags
if (opt.importStyle) $("style").each(function() {
$head.append(this.outerHTML);
});
// add title of the page
if (opt.pageTitle) $head.append("<title>" + opt.pageTitle + "</title>");
// import additional stylesheet(s)
if (opt.loadCSS) {
if ($.isArray(opt.loadCSS)) {
jQuery.each(opt.loadCSS, function(index, value) {
$head.append("<link type='text/css' rel='stylesheet' href='" + this + "'>");
});
} else {
$head.append("<link type='text/css' rel='stylesheet' href='" + opt.loadCSS + "'>");
}
}
var pageHtml = $('html')[0];
// CSS VAR in html tag when dynamic apply e.g. document.documentElement.style.setProperty("--foo", bar);
$doc.find('html').prop('style', pageHtml.style.cssText);
// copy 'root' tag classes
var tag = opt.copyTagClasses;
if (tag) {
tag = tag === true ? 'bh' : tag;
if (tag.indexOf('b') !== -1) {
$body.addClass($('body')[0].className);
}
if (tag.indexOf('h') !== -1) {
$doc.find('html').addClass(pageHtml.className);
}
}
// copy ':root' tag classes
tag = opt.copyTagStyles;
if (tag) {
tag = tag === true ? 'bh' : tag;
if (tag.indexOf('b') !== -1) {
$body.attr('style', $('body')[0].style.cssText);
}
if (tag.indexOf('h') !== -1) {
$doc.find('html').attr('style', pageHtml.style.cssText);
}
}
// print header
appendContent($body, opt.header);
if (opt.canvas) {
// add canvas data-ids for easy access after cloning.
var canvasId = 0;
// .addBack('canvas') adds the top-level element if it is a canvas.
$element.find('canvas').addBack('canvas').each(function(){
$(this).attr('data-printthis', canvasId++);
});
}
appendBody($body, $element, opt);
if (opt.canvas) {
// Re-draw new canvases by referencing the originals
$body.find('canvas').each(function(){
var cid = $(this).data('printthis'),
$src = $('[data-printthis="' + cid + '"]');
this.getContext('2d').drawImage($src[0], 0, 0);
// Remove the markup from the original
if ($.isFunction($.fn.removeAttr)) {
$src.removeAttr('data-printthis');
} else {
$.each($src, function(i, el) {
el.removeAttribute('data-printthis');
});
}
});
}
// remove inline styles
if (opt.removeInline) {
// Ensure there is a selector, even if it's been mistakenly removed
var selector = opt.removeInlineSelector || '*';
// $.removeAttr available jQuery 1.7+
if ($.isFunction($.removeAttr)) {
$body.find(selector).removeAttr("style");
} else {
$body.find(selector).attr("style", "");
}
}
// print "footer"
appendContent($body, opt.footer);
// attach event handler function to beforePrint event
function attachOnBeforePrintEvent($iframe, beforePrintHandler) {
var win = $iframe.get(0);
win = win.contentWindow || win.contentDocument || win;
if (typeof beforePrintHandler === "function") {
if ('matchMedia' in win) {
win.matchMedia('print').addListener(function(mql) {
if(mql.matches) beforePrintHandler();
});
} else {
win.onbeforeprint = beforePrintHandler;
}
}
}
attachOnBeforePrintEvent($iframe, opt.beforePrintEvent);
setTimeout(function() {
if ($iframe.hasClass("MSIE")) {
// check if the iframe was created with the ugly hack
// and perform another ugly hack out of neccessity
window.frames["printIframe"].focus();
$head.append("<script> window.print(); </s" + "cript>");
} else {
// proper method
if (document.queryCommandSupported("print")) {
$iframe[0].contentWindow.document.execCommand("print", false, null);
} else {
$iframe[0].contentWindow.focus();
$iframe[0].contentWindow.print();
}
}
// remove iframe after print
if (!opt.debug) {
setTimeout(function() {
$iframe.remove();
}, 1000);
}
// after print callback
if (typeof opt.afterPrint === "function") {
opt.afterPrint();
}
}, opt.printDelay);
}, 333);
};
// defaults
$.fn.printThis.defaults = {
debug: false, // show the iframe for debugging
importCSS: true, // import parent page css
importStyle: true, // import style tags
printContainer: true, // print outer container/$.selector
loadCSS: "", // path to additional css file - use an array [] for multiple
pageTitle: "", // add title to print page
removeInline: false, // remove inline styles from print elements
removeInlineSelector: "*", // custom selectors to filter inline styles. removeInline must be true
printDelay: 1000, // variable print delay
header: null, // prefix to html
footer: null, // postfix to html
base: false, // preserve the BASE tag or accept a string for the URL
formValues: true, // preserve input/form values
canvas: true, // copy canvas content
doctypeString: '<!DOCTYPE html>', // enter a different doctype for older markup
removeScripts: false, // remove script tags from print content
copyTagClasses: true, // copy classes from the html & body tag
copyTagStyles: true, // copy styles from html & body tag (for CSS Variables)
beforePrintEvent: null, // callback function for printEvent in iframe
beforePrint: null, // function called before iframe is filled
afterPrint: null // function called before iframe is removed
};
})(jQuery);

View File

@ -77,6 +77,7 @@
<script src="{{ asset('assets/js/bootstrap-datepicker.min.js') }}"></script>
<script src="{{ asset('assets/js/helper.js') }}"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-datetimepicker/2.5.20/jquery.datetimepicker.full.min.js" integrity="sha512-AIOTidJAcHBH2G/oZv9viEGXRqDNmfdPVPYOYKGy3fti0xIplnlgMHUGfuNRzC6FkzIo0iIxgFnr9RikFxK+sw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.34/moment-timezone-with-data.min.js"></script> -->
<script>
const AppState = {
@ -87,6 +88,8 @@
route_segment1: "{{ Request::segment(1) }}",
route_segment2: "{{ Request::segment(2) }}",
current_company: 0, // cptid
// TIMEFIX: new Date().getTimezoneOffset() * -60
TIMEFIX: 25200
}
// function startTime() {

View File

@ -0,0 +1,208 @@
<style>
#map {
height: 100%;
margin: 0;
}
.my-leaflet-map-container img {
max-height: none;
}
.dtl-text{
font-size: 11px;
}
.head-text{
font-size: 12px !important;
}
/* .leaflet-overlay-pane svg {
transform: none !important;
} */
.leaflet-routing-container {
display: none !important;
}
#viewPdf {
display: flex;
justify-content: center;
width: 794px;
/* height: auto; */
max-width: 100%; /* Ensures it is responsive */
}
/* .modal-dialog{
width: 794px;
} */
</style>
<div class="modal-dialog modal-dialog modal-dialog-centered modal-dialog-scrollable modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="mdlDetailTripLabel">{{$user->first_name}} Logs</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-4">
<div class="form-group">
<label class="text-muted">From</label>
<!-- default today -->
<!-- <input class="form-control" id="tgl0" value="02-09-2025 00:00"> -->
<input class="form-control" id="tgl0" value="{{ date('d-m-Y 00:00') }}">
</div>
</div>
<div class="col-4">
<div class="form-group">
<label class="text-muted">To</label>
<!-- <input class="form-control" id="tgl1" value="02-09-2025 23:00"> -->
<input class="form-control" id="tgl1" value="{{ date('d-m-Y 23:59') }}">
</div>
</div>
<div class="col-4 d-flex align-items-end">
<button class="btn btn-primary" id="submitFilter">Submit</button>
</div>
</div>
<div class="table-responsive">
<table id="tLogs" class="table table-hover dataTable w-100">
<thead>
<tr class="">
<th class="">Time</th>
<th class="">Module</th>
<th class="">Description</th>
<th class="">Action</th>
</tr>
</thead>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-danger" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
<script>
$(document).ready(function(){
$('.time').each(function () {
const unix = parseInt($(this).text().trim()) + AppState.TIMEFIX;
$(this).text(moment.unix(unix).format('DD MMM YYYY HH:mm:ss'));
});
$('#tgl0, #tgl1').datetimepicker({
format:'d-m-Y H:i',
defaultTime:'00:00',
closeOnDateSelect: true,
// mask:true
});
$('#submitFilter').on('click', function() {
DTable.reload();
});
// $('#submitFilter').trigger('click');
const DTable = {
table: null,
lastAjax: null, // keep track of the last ajax request
activate: function() {
DTable.reload();
},
reload: function() {
// Abort the last request if it's still running
if (DTable.lastAjax) {
DTable.lastAjax.abort();
}
if (DTable.table) {
// If table already exists → reload
DTable.table.ajax.reload();
return;
}
DTable.table = $('#tLogs').DataTable({
searching: false, // 🔹 remove search box
ordering: false, // 🔹 disable sorting for all columns
processing: true,
serverSide: false,
bLengthChange: true,
deferRender: true,
destroy: true,
ajax: function(data, callback, settings) {
// Abort previous
if (DTable.lastAjax) {
DTable.lastAjax.abort();
}
// Fire new request
DTable.lastAjax = $.ajax({
url: `{{ route('api_user_logs1') }}?
tgl0=${moment($('#tgl0').val(), "DD-MM-YYYY HH:mm").unix()}
&tgl1=${moment($('#tgl1').val(), "DD-MM-YYYY HH:mm").unix()}
&id={{$user->id}}
`,
type: 'GET',
success: function(json) {
callback(json);
},
error: function(xhr, status, error) {
if (status !== 'abort') {
console.error("AJAX error:", error);
}
},
complete: function() {
DTable.lastAjax = null;
}
});
},
deferRender: true,
columns: [
{
data: 'crt',
render: (data, type, row, meta) => {
// let addr = row
return `
${moment.unix(data).format('DD MMM YYYY HH:mm:ss')}<br>
`;
}
},
{
data: 'log',
render: (data, type, row, meta) => {
let log = JSON.parse(data);
let module = log.module || '-';
return module;
}
},
{
data: 'log',
render: (data, type, row, meta) => {
let log = JSON.parse(data);
let desc = log.desc || '-';
return desc;
}
},
{
data: 'log',
render: (data, type, row, meta) => {
let log = JSON.parse(data);
let action = log.action || '-';
// use badge for action
// action = `<span class="badge bg-secondary">${action}</span>`;
let badge = '';
if (action.toLowerCase() === 'create') {
badge = `<span class="badge bg-success">${action}</span>`;
} else if (action.toLowerCase() === 'update') {
badge = `<span class="badge bg-warning">${action}</span>`;
} else if (action.toLowerCase() === 'delete') {
badge = `<span class="badge bg-danger">${action}</span>`;
} else {
badge = `<span class="badge bg-secondary">${action}</span>`;
}
return badge;
}
},
],
paging: false,
});
},
};
DTable.activate();
});
</script>

View File

@ -46,6 +46,7 @@
<th class="text-end">Type</th>
<th class="text-center">Status</th>
<th class="text-center">Installation</th>
<th class="text-center">Vehicle</th>
<th class="text-center">Availability</th>
</tr>
</thead>
@ -491,6 +492,13 @@
}
}
},
{
data: 'vhc_nopol1',
className: 'text-left text-nowrap',
visible: true,
orderable: true,
searchable: true,
},
{
data: 'is_available',
className: 'text-center text-nowrap',

View File

@ -0,0 +1,390 @@
@extends('app.app')
@section('title')
Conf Distribution Categorys
@endsection
@section('customcss')
<style>
</style>
@endsection
@section('content')
<div class="container-fluid">
<div class="content">
<div class="card">
<div class="card-header">
<div class="row d-flex align-items-center">
<div class="col-3">
<p class="card-title text-bold mb-0">Distribution Category</p>
</div>
@can('config_distribution_category.create')
<div class="col text-end">
<button id="btnMdlNew" class="btn btn-sm btn-danger">Add New Distribution Category</button>
</div>
@endcan
<div class="col-auto text-end ps-0">
{{-- <button class="btn btn-sm btn-danger">Upload</button> --}}
{{-- <button class="btn btn-sm btn-danger">Download</button> --}}
</div>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="tList" class="table table-hover dataTable w-100">
<thead>
<tr class="">
<th class="">#</th>
<th class="text-center">Action</th>
<th class="">Code</th>
<th class="">Name</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" data-bs-backdrop="static" data-bs-keyboard="false" id="mdlNew" aria-labelledby="mdlLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="mdlLabel">Add New Category</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="fNew">
<!--input hidden -->
<input type="hidden" name="tipe" id="tipe" value="new">
<input type="hidden" name="id" id="id">
<div class="mb-3">
<div class="row">
<div class="col-12">
<label for="dc_code" class="col-form-label">Code:</label>
<input type="text" name="dc_code" id="dc_code" class="form-control" placeholder="Category code, example: VF, VO, etc">
</div>
<div class="col-12">
<label for="dc_name" class="col-form-label">Name:</label>
<input type="text" name="dc_name" id="dc_name" class="form-control" placeholder="Category name">
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@can('config_distribution_category.delete')
<button type="button" id="btnDel" class="btn btn-sm btn-warning">Delete ?</button>
@endcan
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Close</button>
<button id="btnSubmit" type="button" class="btn btn-sm btn-danger">Submit data</button>
@can('config_distribution_category.edit')
<button id="btnEdit" type="button" class="btn btn-sm btn-danger">Update data</button>
@endcan
</div>
</div>
</div>
</div>
<div class="modal fade" data-bs-backdrop="static" data-bs-keyboard="false" id="mdlDel" aria-labelledby="mdlDelLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="mdlDelLabel">Delete TruckType</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="d-flex justify-content-center">
<p class="mb-0">
Are you sure want to delete this Distribution Category
<a href="#" class="text-danger">
<span id="del-type_name"></span>
</a>
</p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-danger" data-bs-dismiss="modal">Close</button>
<button id="btnSubmitDel" type="button" class="btn btn-sm btn-secondary">Yes, delete</button>
</div>
</div>
</div>
</div>
@endsection
@section('customjs')
<script src="{{ asset('assets/js/load-image.all.min.js') }}"></script>
<script>
'use strict';
const State = {
file_jimp_worker: "{{ asset('assets/js/worker/jimp.js') }}",
storage_lara: "{{ asset('storage') }}/",
delay_typing_front: 1000,
};
const Wrapper = {
activate: function() {
Wrapper.event();
DTable.activate();
},
event: function() {
$('#btnMdlNew').on('click', function() {
$('#fNew')[0].reset();
$('#btnDel').hide();
$('#btnEdit').hide();
$('#btnSubmit').show();
$('#mdlLabel').text('New Distribution Category');
$('#dc_code').focus();
$('#mdlNew').modal('show');
});
$('#btnSubmit, #btnEdit').on('click', function() {
let data=$("#fNew").serialize()
Wrapper.submitData(data);
});
$('#tList').on('click', '.btnUpdt', function(e) {
// console.log("updt klik");
const id = $(this).data('id');
$.ajax({
url: "{{ route('api_conf_show_distribution_category', '') }}/" + id,
method: 'GET',
crossDomain: true,
processData: true,
headers: {
'x-api-key': Helper.getCookie('_trtk'),
},
// data: params,
success: (data, textStatus, jqXHR) => {
console.log("res data", data);
if (data.meta.type != 'success') {
resolve({
type: 'fail'
});
Helper.toast('Warning', 'just now', data.meta.message);
return false;
}
// insert value
$('#mdlLabel').text('Edit Distribution Category');
$('#btnDel').show();
$('#btnEdit').show();
$('#btnSubmit').hide();
$('#tipe').val("edit");
$('#id').val(id);
$('#dc_code').val(data.data.dc_code);
$('#dc_name').val(data.data.dc_name);
},
error: (jqXHR, textStatus, error) => {
if (jqXHR.status >= 500) {
Helper.toast('Error', 'just now', 'please try again');
} else {
Helper.toast('Error', 'just now', jqXHR.responseJSON.meta
.message);
}
resolve({
type: 'error'
});
}
})
$('#mdlNew').modal('show');
});
$('#btnDel').on('click', function(e) {
$('#mdlDel').modal('show');
});
$('#btnSubmitDel').on('click', function(e) {
const id = $('#id').val()
const data = {
id: $('#mdlDel').data('id'),
};
$('#btnSubmitDel').attr('disabed', true);
$.ajax({
url: "{{ route('api_conf_del_distribution_category', '') }}/" + id,
method: 'DELETE',
crossDomain: true,
processData: true,
headers: {
'x-api-key': Helper.getCookie('_trtk'),
'x-csrf-token': $('meta[name="csrf-token"]').attr('content'),
},
data: data,
success: (data, textStatus, jqXHR) => {
$('#btnSubmitDel').removeAttr('disabed');
if (data.meta.type != 'success') {
resolve({
type: 'fail'
});
Helper.toast('Warning', 'just now', data.meta.message);
return false;
}
Helper.toast('Success', 'just now', 'success delete Distribution Category');
$('#mdlDel').modal('hide');
$('#mdlNew').modal('hide');
$('#tList').DataTable().ajax.reload();
resolve({
type: 'success'
});
},
error: (jqXHR, textStatus, error) => {
$('#btnSubmitDel').removeAttr('disabed');
if (jqXHR.status >= 500) {
Helper.toast('Error', 'just now', 'Please try again');
} else {
Helper.toast('Error', 'just now', jqXHR.responseJSON.meta
.message);
}
resolve({
type: 'error'
});
}
})
})
$('#mdlDel').on('show.bs.modal', function () {
$('#mdlNew').css('opacity', '0.5'); // fade background modal
});
$('#mdlDel').on('hidden.bs.modal', function () {
$('#mdlNew').css('opacity', '1');
});
},
submitData: async function(data) {
return new Promise((resolve, reject) => {
if (typeof $('#btnSubmit').attr('disabed') != 'undefined') {
resolve({
type: 'fail'
});
return false;
}
$('#btnSubmit').attr('disabed', true);
$.ajax({
url: "{{ route('api_conf_add_distribution_category') }}",
method: 'POST',
crossDomain: true,
processData: true,
headers: {
'x-api-key': Helper.getCookie('_trtk'),
'x-csrf-token': $('meta[name="csrf-token"]').attr('content'),
},
data: data,
success: (data, textStatus, jqXHR) => {
$('#btnSubmit').removeAttr('disabed');
if (data.meta.type != 'success') {
resolve({
type: 'fail'
});
Helper.toast('Warning', 'just now', data.meta.message);
return false;
}
Helper.toast('Success', 'just now', 'success add new Distribution Category');
$('#mdlNew').modal('hide');
$('#tList').DataTable().ajax.reload();
resolve({
type: 'success'
});
},
error: (jqXHR, textStatus, error) => {
$('#btnSubmit').removeAttr('disabed');
if (jqXHR.status >= 500) {
Helper.toast('Error', 'just now', 'Please try again');
} else {
Helper.toast('Error', 'just now', jqXHR.responseJSON.meta
.message);
}
resolve({
type: 'error'
});
}
})
})
},
};
const DTable = {
activate: function() {
DTable.reload();
},
reload: function() {
// $('#tList').DataTable();
// if (Driver.Table.firstInitDataTable == 1) { loadTableSkeletonLoading() } else { Driver.Table.firstInitDataTable = 1; }
$('#tList').DataTable({
processing: true,
serverSide: false,
bLengthChange: true,
deferRender: true,
destroy: true,
ajax: {
url: "{{ route('api_conf_list_distribution_category') }}",
type: 'GET',
complete: function(jqXHR, textStatus, c) {
let count = jqXHR.responseJSON.count;
if (typeof count != 'undefined') {
$('#count_trucktypes').text(count);
}
// removeTableSkeletonLoading()
},
},
deferRender: true,
columns: [{
data: 'DT_RowIndex',
className: 'text-end',
visible: true,
orderable: true,
searchable: true,
width: "30px"
},
{
data: 'action',
className: 'text-center',
visible: true,
orderable: true,
searchable: true,
width: "100px",
render: function(data, type, row, meta) {
let action = `
<a href="#" class="text-decoration-none me-1 btnUpdt" data-id="${row.id}" title="Update" data-bs-toggle="tooltip" data-bs-placement="top">
<span class="icon ion-eye fz-16"></span>
</a>
`;
return action;
}
},
{
data: 'dc_code',
className: 'text-left text-nowrap',
visible: true,
orderable: true,
searchable: true,
createdCell: function(td, cellData, rowData, row, col) {
$(td).attr('data-ttid', rowData.id);
$(td).attr('data-type_name', rowData.type_name);
},
},
{
data: 'dc_name',
className: 'text-left',
visible: true,
orderable: true,
searchable: true,
},
],
});
},
};
Wrapper.activate();
</script>
@endsection

View File

@ -0,0 +1,390 @@
@extends('app.app')
@section('title')
Conf Pools
@endsection
@section('customcss')
<style>
</style>
@endsection
@section('content')
<div class="container-fluid">
<div class="content">
<div class="card">
<div class="card-header">
<div class="row d-flex align-items-center">
<div class="col-3">
<p class="card-title text-bold mb-0">Pool / Area</p>
</div>
@can('config_pool.create')
<div class="col text-end">
<button id="btnMdlNew" class="btn btn-sm btn-danger">Add New Pool</button>
</div>
@endcan
<div class="col-auto text-end ps-0">
{{-- <button class="btn btn-sm btn-danger">Upload</button> --}}
{{-- <button class="btn btn-sm btn-danger">Download</button> --}}
</div>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="tList" class="table table-hover dataTable w-100">
<thead>
<tr class="">
<th class="">#</th>
<th class="text-center">Action</th>
<th class="">Code</th>
<th class="">Name</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" data-bs-backdrop="static" data-bs-keyboard="false" id="mdlNew" aria-labelledby="mdlLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="mdlLabel">Add New Pool</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form id="fNew">
<!--input hidden -->
<input type="hidden" name="tipe" id="tipe" value="new">
<input type="hidden" name="id" id="id">
<div class="mb-3">
<div class="row">
<div class="col-12">
<label for="pool_code" class="col-form-label">Code:</label>
<input type="text" name="pool_code" id="pool_code" class="form-control" placeholder="Pool code, example: VF, VO, etc">
</div>
<div class="col-12">
<label for="pool_name" class="col-form-label">Name:</label>
<input type="text" name="pool_name" id="pool_name" class="form-control" placeholder="Pool name">
</div>
</div>
</div>
</form>
</div>
<div class="modal-footer">
@can('config_pool.delete')
<button type="button" id="btnDel" class="btn btn-sm btn-warning">Delete ?</button>
@endcan
<button type="button" class="btn btn-sm btn-secondary" data-bs-dismiss="modal">Close</button>
<button id="btnSubmit" type="button" class="btn btn-sm btn-danger">Submit data</button>
@can('config_pool.edit')
<button id="btnEdit" type="button" class="btn btn-sm btn-danger">Update data</button>
@endcan
</div>
</div>
</div>
</div>
<div class="modal fade" data-bs-backdrop="static" data-bs-keyboard="false" id="mdlDel" aria-labelledby="mdlDelLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-sm">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="mdlDelLabel">Delete TruckType</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="d-flex justify-content-center">
<p class="mb-0">
Are you sure want to delete this Pool
<a href="#" class="text-danger">
<span id="del-type_name"></span>
</a>
</p>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-danger" data-bs-dismiss="modal">Close</button>
<button id="btnSubmitDel" type="button" class="btn btn-sm btn-secondary">Yes, delete</button>
</div>
</div>
</div>
</div>
@endsection
@section('customjs')
<script src="{{ asset('assets/js/load-image.all.min.js') }}"></script>
<script>
'use strict';
const State = {
file_jimp_worker: "{{ asset('assets/js/worker/jimp.js') }}",
storage_lara: "{{ asset('storage') }}/",
delay_typing_front: 1000,
};
const Wrapper = {
activate: function() {
Wrapper.event();
DTable.activate();
},
event: function() {
$('#btnMdlNew').on('click', function() {
$('#fNew')[0].reset();
$('#btnDel').hide();
$('#btnEdit').hide();
$('#btnSubmit').show();
$('#mdlLabel').text('New Pool');
$('#pool_code').focus();
$('#mdlNew').modal('show');
});
$('#btnSubmit, #btnEdit').on('click', function() {
let data=$("#fNew").serialize()
Wrapper.submitData(data);
});
$('#tList').on('click', '.btnUpdt', function(e) {
// console.log("updt klik");
const id = $(this).data('id');
$.ajax({
url: "{{ route('api_conf_show_pool', '') }}/" + id,
method: 'GET',
crossDomain: true,
processData: true,
headers: {
'x-api-key': Helper.getCookie('_trtk'),
},
// data: params,
success: (data, textStatus, jqXHR) => {
console.log("res data", data);
if (data.meta.type != 'success') {
resolve({
type: 'fail'
});
Helper.toast('Warning', 'just now', data.meta.message);
return false;
}
// insert value
$('#mdlLabel').text('Edit Pool');
$('#btnDel').show();
$('#btnEdit').show();
$('#btnSubmit').hide();
$('#tipe').val("edit");
$('#id').val(id);
$('#pool_code').val(data.data.pool_code);
$('#pool_name').val(data.data.pool_name);
},
error: (jqXHR, textStatus, error) => {
if (jqXHR.status >= 500) {
Helper.toast('Error', 'just now', 'please try again');
} else {
Helper.toast('Error', 'just now', jqXHR.responseJSON.meta
.message);
}
resolve({
type: 'error'
});
}
})
$('#mdlNew').modal('show');
});
$('#btnDel').on('click', function(e) {
$('#mdlDel').modal('show');
});
$('#btnSubmitDel').on('click', function(e) {
const id = $('#id').val()
const data = {
id: $('#mdlDel').data('id'),
};
$('#btnSubmitDel').attr('disabed', true);
$.ajax({
url: "{{ route('api_conf_del_pool', '') }}/" + id,
method: 'DELETE',
crossDomain: true,
processData: true,
headers: {
'x-api-key': Helper.getCookie('_trtk'),
'x-csrf-token': $('meta[name="csrf-token"]').attr('content'),
},
data: data,
success: (data, textStatus, jqXHR) => {
$('#btnSubmitDel').removeAttr('disabed');
if (data.meta.type != 'success') {
resolve({
type: 'fail'
});
Helper.toast('Warning', 'just now', data.meta.message);
return false;
}
Helper.toast('Success', 'just now', 'success delete Pool');
$('#mdlDel').modal('hide');
$('#mdlNew').modal('hide');
$('#tList').DataTable().ajax.reload();
resolve({
type: 'success'
});
},
error: (jqXHR, textStatus, error) => {
$('#btnSubmitDel').removeAttr('disabed');
if (jqXHR.status >= 500) {
Helper.toast('Error', 'just now', 'Please try again');
} else {
Helper.toast('Error', 'just now', jqXHR.responseJSON.meta
.message);
}
resolve({
type: 'error'
});
}
})
})
$('#mdlDel').on('show.bs.modal', function () {
$('#mdlNew').css('opacity', '0.5'); // fade background modal
});
$('#mdlDel').on('hidden.bs.modal', function () {
$('#mdlNew').css('opacity', '1');
});
},
submitData: async function(data) {
return new Promise((resolve, reject) => {
if (typeof $('#btnSubmit').attr('disabed') != 'undefined') {
resolve({
type: 'fail'
});
return false;
}
$('#btnSubmit').attr('disabed', true);
$.ajax({
url: "{{ route('api_conf_add_pool') }}",
method: 'POST',
crossDomain: true,
processData: true,
headers: {
'x-api-key': Helper.getCookie('_trtk'),
'x-csrf-token': $('meta[name="csrf-token"]').attr('content'),
},
data: data,
success: (data, textStatus, jqXHR) => {
$('#btnSubmit').removeAttr('disabed');
if (data.meta.type != 'success') {
resolve({
type: 'fail'
});
Helper.toast('Warning', 'just now', data.meta.message);
return false;
}
Helper.toast('Success', 'just now', 'success add new Pool');
$('#mdlNew').modal('hide');
$('#tList').DataTable().ajax.reload();
resolve({
type: 'success'
});
},
error: (jqXHR, textStatus, error) => {
$('#btnSubmit').removeAttr('disabed');
if (jqXHR.status >= 500) {
Helper.toast('Error', 'just now', 'Please try again');
} else {
Helper.toast('Error', 'just now', jqXHR.responseJSON.meta
.message);
}
resolve({
type: 'error'
});
}
})
})
},
};
const DTable = {
activate: function() {
DTable.reload();
},
reload: function() {
// $('#tList').DataTable();
// if (Driver.Table.firstInitDataTable == 1) { loadTableSkeletonLoading() } else { Driver.Table.firstInitDataTable = 1; }
$('#tList').DataTable({
processing: true,
serverSide: false,
bLengthChange: true,
deferRender: true,
destroy: true,
ajax: {
url: "{{ route('api_conf_list_pool') }}",
type: 'GET',
complete: function(jqXHR, textStatus, c) {
let count = jqXHR.responseJSON.count;
if (typeof count != 'undefined') {
$('#count_trucktypes').text(count);
}
// removeTableSkeletonLoading()
},
},
deferRender: true,
columns: [{
data: 'DT_RowIndex',
className: 'text-end',
visible: true,
orderable: true,
searchable: true,
width: "30px"
},
{
data: 'action',
className: 'text-center',
visible: true,
orderable: true,
searchable: true,
width: "100px",
render: function(data, type, row, meta) {
let action = `
<a href="#" class="text-decoration-none me-1 btnUpdt" data-id="${row.id}" title="Update" data-bs-toggle="tooltip" data-bs-placement="top">
<span class="icon ion-eye fz-16"></span>
</a>
`;
return action;
}
},
{
data: 'pool_code',
className: 'text-left text-nowrap',
visible: true,
orderable: true,
searchable: true,
createdCell: function(td, cellData, rowData, row, col) {
$(td).attr('data-ttid', rowData.id);
$(td).attr('data-type_name', rowData.type_name);
},
},
{
data: 'pool_name',
className: 'text-left',
visible: true,
orderable: true,
searchable: true,
},
],
});
},
};
Wrapper.activate();
</script>
@endsection

File diff suppressed because it is too large Load Diff

View File

@ -408,10 +408,12 @@
<div class="mb-2">
<label class="text-muted">From</label>
<input class="form-control" id="historyStartDate">
<!-- <input class="form-control" id="historyStartDate" value="11-08-2025 00:00"> -->
</div>
<div class="mb-2">
<label class="text-muted">To</label>
<input class="form-control" id="historyEndDate">
<!-- <input class="form-control" id="historyEndDate" value="11-08-2025 23:00"> -->
</div>
<button class="btn btn-primary btn-sm w-100 mb-3" id="historySearch">Search</button>
</div>
@ -533,6 +535,7 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js" integrity="sha512-ozq8xQKq6urvuU6jNgkfqAmT7jKN2XumbrX1JiB3TnF7tI48DPI4Gy1GXKD/V3EExgAs1V+pRO7vwtS1LHg0Gw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.jsdelivr.net/npm/leaflet-rotatedmarker@0.2.0/leaflet.rotatedMarker.min.js"></script>
<script src="https://unpkg.com/leaflet-routing-machine@3.2.12/dist/leaflet-routing-machine.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
'use strict';
@ -941,27 +944,116 @@
if (cb) cb(polyline);
return polyline;
},
addRoutes: function(locs = [], cb = null) {
addRoutes: async function(locs = [], cb = null) {
let latLngs = [];
for (let i = 0; i < locs.length; i++) {
latLngs.push([locs[i].lat, locs[i].lng]);
}
let routes = L.Routing.control({
waypoints: latLngs.reverse(), // reverse to make first point as start
// router: L.Routing.osrmv1(),
router: L.Routing.osrmv1({
serviceUrl: "https://brilianapps.britimorleste.tl:5001/route/v1",
}),
show: false,
itinerary: null, // 👈 completely removes the panel
addWaypoints: false, // ❌ prevent adding points by clicking
draggableWaypoints: false, // ❌ prevent dragging markers
routeWhileDragging: false, // optional: dont reroute while dragging
createMarker: () => null,
lineOptions:{
styles: [{ color: locs[0]?.options?.color, weight: 3, opacity: 0.7 }],
function chunkArray(arr, size) {
const result = [];
for (let i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, i + size));
}
return result;
}
function fetchOsrm(points) {
const coords = points;
const hints = ";".repeat(points.length - 1);
const body = {
coordinates: coords,
overview: "false",
alternatives: "true",
steps: "true",
hints: hints
};
let config = {
method: 'post',
maxBodyLength: Infinity,
url: 'https://brilianapps.britimorleste.tl/osrm-backend/post-route/v1/driving/',
headers: {
'Content-Type': 'application/json'
},
}).addTo(Leaflet.map)
data: body
};
return axios.request(config)
.then((response) => {
return response.data;
})
.catch((err) => {
console.error("Error:", err.message);
return null;
});
}
function decodeOSRMGeometry(encoded) {
const coordinates = [];
let index = 0, lat = 0, lng = 0;
while (index < encoded.length) {
let result = 1, shift = 0, b;
do {
b = encoded.charCodeAt(index++) - 63 - 1;
result += b << shift;
shift += 5;
} while (b >= 0x1f);
lat += (result & 1 ? ~(result >> 1) : result >> 1);
result = 1;
shift = 0;
do {
b = encoded.charCodeAt(index++) - 63 - 1;
result += b << shift;
shift += 5;
} while (b >= 0x1f);
lng += (result & 1 ? ~(result >> 1) : result >> 1);
coordinates.push([lat / 1e5, lng / 1e5]);
}
return coordinates;
}
async function getCoordinates(points) {
const chunkSize = 500;
const chunks = chunkArray(points, chunkSize); // Split the points array into chunks of 500
let allCoords = [];
for (const chunk of chunks) {
const osrm = await fetchOsrm(chunk); // Fetch OSRM data for each chunk
if (!osrm) {
console.log("OSRM failed for chunk");
return;
}
const coords = osrm.routes[0].legs.flatMap(leg =>
leg.steps.flatMap(step =>
decodeOSRMGeometry(step.geometry)
)
);
allCoords = allCoords.concat(coords); // Combine the result
}
// Now add the polyline to the map
return L.polyline(allCoords, {
color: locs[0]?.options?.color,
weight: 3,
opacity: 0.8
}).addTo(Leaflet.map);
// map.fitBounds(allCoords.map(c => L.latLng(c[0], c[1])));
}
// Usage: Pass the array of coordinates to the function
const routes = await getCoordinates(latLngs);
// optionally fit bounds
// Leaflet.map.fitBounds(coords.map(c => L.latLng(c[0], c[1])));
if (cb) cb(routes);
return routes;
@ -1310,8 +1402,9 @@
$('#infoMovement').addClass('d-block');
if (State.inShowVid) {
let cache = !(State.inShowVid != State.loadedLastMoveVid);
Trucks.bundleShowRouteTruck(cache); // jika data yang diload sebelumnya beda, maka tidak mengambil dari cache
Trucks.bundleShowRouteTruck(cache, false); // jika data yang diload sebelumnya beda, maka tidak mengambil dari cache
}
// Menu.clearListMovements();
}
},
eventListVehicle: function() {
@ -1354,48 +1447,14 @@
});
},
showToListMovement: function(obj) {
/**
* fix DOMException: Failed to execute 'btoa' on 'Window': The string to be encoded contains
* because of string CawangPluit
* solution: https://stackoverflow.com/questions/23223718/failed-to-execute-btoa-on-window-the-string-to-be-encoded-contains-characte
* window.btoa( unescape(encodeURIComponent(str) ) is deprecated => window.btoa(encodeURIComponent(str))
*/
// <p class="text-muted mb-0">${(obj?.city_text || obj?.state_text || 'address')} - ${(obj?.postcode || 'postcode')}</p>
// <p class="text-muted mb-0">${moment.unix(obj?.lst_loc_crt).format('DD MMM YYYY HH:mm')}</p> // yang ini mix
// <p class="text-success mb-0">Device Time: ${moment.unix(obj?.lst_loc_crt_d).format('DD MMM YYYY HH:mm')}</p>
// <p class="text-danger mb-0">Server Time: ${moment.unix(obj?.lst_loc_crt_s).format('DD MMM YYYY HH:mm')}</p>
// <p class="${(obj.pre_milleage >= 3) ? 'text-warning' : 'text-muted'} mb-0">Pre Milleage: ${(obj.pre_milleage).toFixed(3)} Km</p>
// $('#infoMove-plots').append(`
// <a href="#" class="plotMove-item" data-obj="${window.btoa( encodeURIComponent( JSON.stringify(obj) ) )}">
// <li class="list-group-item p-1 px-2">
// <p class="text-bold mb-0">${obj.key_index}</p>
// <p class="text-bold mb-0">Time: ${moment.unix(obj?.lst_loc_crt_d).format('DD MMM YYYY HH:mm')}</p>
// <p class="text-muted mb-0">Speed: ${(typeof obj.speed != 'undefined') ? obj.speed : '0'}</p>
// <p class="text-muted mb-0">${Number(obj.latitude).toFixed(5)} - ${Number(obj.longitude).toFixed(6)}</p>
// <p class="text-muted mb-0">${Helper.shortenText(decodeURIComponent(obj?.fulladdress || 'address'), 25)}</p>
// </li>
// </a>
// `);
let arrIdx = Helper.getIndexReversedSequence(obj.key_index - 1, Trucks.last_move.length - 1);
// +7
// $('#infoMove-plots').append(`
// <a href="#" class="plotMove-item" data-obj="${window.btoa( encodeURIComponent( JSON.stringify(obj) ) )}">
// <li class="list-group-item p-1 px-2">
// <p class="text-bold mb-0">${arrIdx + 1}</p>
// <p class="text-bold mb-0">Time: ${moment.unix(obj?.lst_loc_crt_d).format('DD MMM YYYY HH:mm:ss')}</p>
// <p class="text-muted mb-0">${Number(obj.latitude).toFixed(5)} - ${Number(obj.longitude).toFixed(6)}</p>
// <p class="text-muted mb-2">${Helper.shortenText(decodeURIComponent(obj?.fulladdress || 'address'), 255)}</p>
// <p class="mb-0">Current speed: ${Number(obj.speed)}km/h</p>
// </li>
// </a>
// `);
const unix = parseInt(obj?.lst_loc_crt_d) + AppState.TIMEFIX;
$('#infoMove-plots').append(`
<a href="#" class="plotMove-item" data-obj="${window.btoa( encodeURIComponent( JSON.stringify(obj) ) )}">
<li class="list-group-item p-1 px-2">
<p class="text-bold mb-0">${arrIdx + 1}</p>
<p class="text-bold mb-0">Time: ${moment.unix(obj?.lst_loc_crt_d).format('DD MMM YYYY HH:mm:ss')}</p>
<p class="text-bold mb-0">Time: ${moment.unix(unix).format('DD MMM YYYY HH:mm:ss')}</p>
<p class="text-muted mb-0">${Number(obj.latitude).toFixed(5)} - ${Number(obj.longitude).toFixed(6)}</p>
<p class="text-muted mb-2">${Helper.shortenText(decodeURIComponent(obj?.fulladdress || 'address'), 255)}</p>
<p class="mb-0">Current speed: ${Number(obj.speed)}km/h</p>
@ -1417,11 +1476,12 @@
},
createMarkerDetailPlotMovement: function(tr, opt = {}) {
Leaflet.clearLayer('eventRemoveDetailPlotMovement');
const unix = parseInt(tr?.lst_loc_crt_d) + AppState.TIMEFIX;
let marker = Leaflet.addMarkers({
lat: tr.latitude,
lng: tr.longitude,
// label: `<b>${tr.key_index}</b><br>${tr.nopol1} ${tr.nopol2} ${tr.nopol3}<br>${moment.unix(tr?.lst_loc_crt).format('DD MMM YYYY HH:mm')}<br>Speed: ${(typeof tr.lst_speed != 'undefined') ? tr.lst_speed : '0'}<br>${tr.latitude},${tr.longitude}<br>${decodeURIComponent(tr?.fulladdress || 'address')}`,
label: `<b>${tr.nopol1} ${tr.nopol2} ${tr.nopol3}</b><br>${moment.unix(tr?.lst_loc_crt).format('DD MMM YYYY HH:mm')}<br>${decodeURIComponent(tr?.fulladdress || 'address')}.<br><br>Current speed: ${tr?.speed}km/h`,
label: `<b>${tr.nopol1} ${tr.nopol2} ${tr.nopol3}</b><br>${moment.unix(unix).format('DD MMM YYYY HH:mm')}<br>${decodeURIComponent(tr?.fulladdress || 'address')}.<br><br>Current speed: ${tr?.speed}km/h`,
//label: `<b>${tr.nopol1} ${tr.nopol2} ${tr.nopol3}</b><br>${moment.unix(tr?.lst_loc_crt_d).utcOffset(9 * 60).format('DD MMM YYYY HH:mm:ss')}<br>${decodeURIComponent(tr?.fulladdress || 'address')}.<br><br>Current speed: ${tr?.speed}km/h`,
options: {
// icon: Icon.destination()
@ -1559,7 +1619,7 @@
$('#historyStartDate').val(moment().startOf('day').format('DD-MM-YYYY HH:mm'));
$('#historyEndDate').val(moment().endOf('day').format('DD-MM-YYYY HH:mm'));
// // for testing purpose
// for testing purpose
// State.historyStartDate = '1756054800';
// State.historyEndDate = '1756141140';
// $('#historyStartDate').val("25-08-2025 00:00");
@ -2576,10 +2636,11 @@
// last movement
// $('#infoVehicles-infoMove').text('Last 24H from ' + moment.unix(truck?.lst_loc_crt).format('DD MMM YYYY HH:mm'));
},
bundleShowRouteTruck: async function(cache = false) {
bundleShowRouteTruck: async function(cache = false, needUpdate = true) {
Menu.clearListMovements();
if (State.inShowVid != State.loadedLastMoveVid) $('#historyStartDate').trigger('clearFilterHistoryDate');
if (State.inShowVid != State.loadedLastMoveVid)$('#historyStartDate').trigger('clearFilterHistoryDate');
if(needUpdate){
const getLastMove = await Trucks.getLastMove(State.inShowVid, cache);
Trucks.last_move = getLastMove.flat()
@ -2589,6 +2650,7 @@
return false;
}
Trucks.showLastMoveToView(getLastMove);
}
},
getLastMove: async function(vid, cache = false) {
State.loadedLastMoveVid = vid;
@ -2741,7 +2803,7 @@
// PgBar.setMinMax(0, truckRoutes.length - 1);
// },
routeStartEndGroupTrip: function(truckRoutes) {
routeStartEndGroupTrip: async function(truckRoutes) {
const colors = [
"#2980B9", // Dark Blue
"#C0392B", // Dark Red
@ -2750,6 +2812,7 @@
"#F39C12", // Dark Yellow/Gold
]
let i = 1
let allStartStop = []
let polyTruckRoutes = truckRoutes.map((groupTrip, key0) => {
return groupTrip.map((obj, key) => {
obj.key_index = i++
@ -2758,12 +2821,12 @@
Menu.showToListMovement(obj)
// add start end marker per group trip
if(key == 0 || key == groupTrip.length - 1)
Leaflet.addMarkers({
if(key == 0 || key == groupTrip.length - 1){
const marker = Leaflet.addMarkers({
lat: obj.latitude,
lng: obj.longitude,
label: `
<b>Start Poin</b><br>${obj.nopol1} ${obj.nopol2} ${obj.nopol3}<br>
<b>${(key == 0) ? 'Finish' : 'Start'} Poin</b><br>${obj.nopol1} ${obj.nopol2} ${obj.nopol3}<br>
${moment.unix(obj?.lst_loc_crt).format('DD MMM YYYY HH:mm')}<br>
Speed: ${(typeof obj.lst_speed != 'undefined') ? obj.lst_speed : '0'}<br>
${Number(obj.latitude).toFixed(5)},${Number(obj.longitude).toFixed(6)}<br>
@ -2775,6 +2838,10 @@
// rotationAngle: 290
}
})
allStartStop.push(marker)
}
const unix = parseInt(obj?.lst_loc_crt_d) + AppState.TIMEFIX;
return {
lat: obj.latitude,
@ -2789,84 +2856,73 @@
radius: 5,
markerOpacity: 0
},
label: `<b>${obj.nopol1} ${obj.nopol2} ${obj.nopol3}</b><br>${moment.unix(obj?.lst_loc_crt_d).format('DD MMM YYYY HH:mm:ss')}<br>${decodeURIComponent(obj?.fulladdress || 'address')}`,
label: `<b>${obj.nopol1} ${obj.nopol2} ${obj.nopol3}</b><br>${moment.unix(unix).format('DD MMM YYYY HH:mm:ss')}<br>${decodeURIComponent(obj?.fulladdress || 'address')}`,
// startLast : (key == 0) ? 0 : (key == (groupTrip.length - 1)) ? 1 : null,
}
})
})
// console.log("truckRoutes update", polyTruckRoutes);
polyTruckRoutes.forEach(async (poly, idx) => {
let circleCounter = 0
for (const [idx, poly] of polyTruckRoutes.entries()) {
// console.log("poly", poly);
let polyline = Leaflet.addRoutes(poly)
// let polyline = Leaflet.addPolylines(poly)
function addCirclesAsync(polyCircle) {
// console.log("circleCounter", circleCounter);
// let circlesStartStop = []
// Leaflet.addCircles(poly, function(circle, i) {
// if(i == 0 || i == (polyTruckRoutes.length - 1))
// circlesStartStop.push(circle)
// window.addEventListener('eventRemoveRouteStartEnd', function handler(e) {
// circle.remove();
// });
// circle.on('click', function() {
// PgBar.syncToPlotTravelHistory(i);
// })
// });
function addCirclesAsync(poly) {
// return array of circles start end only
return new Promise(resolve => {
let circles = [];
Leaflet.addCircles(poly, function(circle, i) {
if (i === 0 || i === (poly.length - 1)) {
Leaflet.addCircles(polyCircle, function(circle, i) {
// console.log("circle", i, circleCounter);
if (i === 0 || i === (polyCircle.length - 1)) {
circles.push(circle);
}
const currentCounter = circleCounter;
circle.on('click', function() {
PgBar.syncToPlotTravelHistory(i);
PgBar.syncToPlotTravelHistory(currentCounter);
});
circleCounter++
// ✅ When last point processed, resolve
if (i === poly.length - 1) {
if (i === polyCircle.length - 1) {
resolve(circles);
}
});
});
}
let circlesStartStop = [];
for (let [idx, poly] of polyTruckRoutes.entries()) {
let polyline = Leaflet.addRoutes(poly);
let polyline = await Leaflet.addRoutes(poly);
// console.log("polyline", polyline);
allStartStop.push(polyline)
// wait for all circles
circlesStartStop = await addCirclesAsync(poly);
// keep event + removal logic here
State.eventRemoveRouteStartEnd = new CustomEvent('eventRemoveRouteStartEnd', {
detail: { polyline, circlesStartStop }
});
window.addEventListener('eventRemoveRouteStartEnd', function(e) {
polyline.remove();
circlesStartStop.forEach(c => c.remove());
});
const ssmarker = await addCirclesAsync(poly);
// console.log("ssmarker", ssmarker);
allStartStop.push(...ssmarker)
}
console.log("allStartStop", allStartStop);
// console.log("circlesStartStop", circlesStartStop);
// // remove marker, circle, event listener and all about this marker
State.eventRemoveRouteStartEnd = new CustomEvent('eventRemoveRouteStartEnd', {
detail: {
polyline,
circlesStartStop
}
// detail: {
allStartStop,
// circlesStartStop
// }
});
window.addEventListener('eventRemoveRouteStartEnd', function (e) {
polyline.remove();
circlesStartStop.forEach(c => c.remove())
});
})
// window.addEventListener('eventRemoveRouteStartEnd', function (e) {
// // allStartStop.remove();
// allStartStop.forEach(c => c.remove())
// // circlesStartStop.forEach(c => c.remove())
// });
window.addEventListener('eventRemoveRouteStartEnd', function handler(e) {
allStartStop.forEach(c => c.remove())
e.currentTarget.removeEventListener(e.type,
handler); // window.removeEventListener('remove', this.handler, true);
State.eventRemoveRouteStartEnd = null;
@ -3436,6 +3492,8 @@
}
},
syncToPlotTravelHistory: function(arrIdx) {
console.log("syncToPlotTravelHistory", arrIdx);
const listPlotTravelHistory = document.querySelectorAll('#infoMove-plots .plotMove-item')[arrIdx];
listPlotTravelHistory.scrollIntoView();
listPlotTravelHistory.querySelector('li').classList.add('hover');

View File

@ -0,0 +1,347 @@
<style>
#map {
height: 100%;
margin: 0;
}
.my-leaflet-map-container img {
max-height: none;
}
.dtl-text{
font-size: 11px;
}
.head-text{
font-size: 12px !important;
}
/* .leaflet-overlay-pane svg {
transform: none !important;
} */
.leaflet-routing-container {
display: none !important;
}
#viewPdf {
display: flex;
justify-content: center;
width: 794px;
/* height: auto; */
max-width: 100%; /* Ensures it is responsive */
}
/* .modal-dialog{
width: 794px;
} */
</style>
<div class="modal-dialog modal-dialog modal-dialog-centered modal-dialog-scrollable modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="mdlDetailTripLabel">{{$nopol1}} Trip Detail</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row head-text" id="viewPdf">
<div class="col-12">
<h4>{{$nopol1}}</h4>
</div>
<div class="col-4">
<p class="text-bold mb-0">Start</p>
<p class="mb-0 time">{{ $start['time'] }}</p>
<p class="mb-0">Vehicle Mileage: {{number_format($start['mileage'], 2)}} km</p>
<p>{{$start['fulladdress']}}</p>
</div>
<div class="col-4">
<p class="text-bold mb-0">Finish</p>
<p class="mb-0 time">{{ $finish['time'] }}</p>
<p class="mb-0">Vehicle Mileage: {{number_format($finish['mileage'], 2)}} km</p>
<p>{{$finish['fulladdress']}}</p>
</div>
<div class="col-2">
<p class="text-bold mb-0">Distance</p>
<p class="mb-0">{{number_format($distance, 2)}} km</p>
</div>
<div class="col-2">
<p class="text-bold mb-0">Duration</p>
<p class="mb-0">{{$duration}}</p>
</div>
<div class="col-12">
<div id="leafMap" style="height: 400px;"></div>
</div>
<div class="col-12">
<!-- <li class="list-group-item p-1 px-2">
<p class="text-bold mb-0">Time: 25 Aug 2025 07:31:08</p>
<p class="text-muted mb-0 dtl-text">-8.55387 - 125.542409</p>
<p class="text-muted mb-0 dtl-text">Avenida Luro Mata, Praia dos Coqueiros, Bebunuk, Dom Aleixo, Dili, Timor-Leste;2066973</p>
<p class="mb-0 dtl-text">Current speed: 7km/h</p>
</li> -->
@foreach ($list as $item)
<!-- <li class="list-group-item p-1 px-2">
<p class="text-bold mb-0">Time: {{date('d-m-Y H:i:s', $item->crt_d)}}</p>
<p class="text-muted mb-0 dtl-text">Vehicle Mileage: {{number_format($item->vhc_milleage, 2)}} km</p>
<p class="text-muted mb-0 dtl-text">{{number_format($item->latitude, 6)}} - {{number_format($item->longitude, 6)}}</p>
<p class="text-muted mb-0 dtl-text">{{urldecode($item->fulladdress)}}</p>
<p class="text-muted mb-0 dtl-text">Current speed: {{number_format($item->speed, 2)}} km/h</p>
</li> -->
<li class="list-group-item p-1 px-2">
<div class="row">
<div class="col-4">
<p class="text-bold mb-0 dtl-text">Time: <span class="time">{{ $item->crt_d }}</span></p>
<p class="text-muted mb-0 dtl-text">Vehicle Mileage: {{number_format($item->vhc_milleage, 2)}} km</p>
<p class="text-muted mb-0 dtl-text">Current speed: {{$item->speed}} km/h</p>
</div>
<div class="col-8">
<p class="text-muted mb-0 dtl-text">{{number_format($item->latitude, 6)}}, {{number_format($item->longitude, 6)}}</p>
<p class="text-muted mb-0 dtl-text">{{urldecode($item->fulladdress)}}</p>
</div>
</div>
</li>
@endforeach
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-sm btn-danger ms-auto" id="btnDownloadReport">Download Report</button>
</div>
</div>
</div>
<script>
$(document).ready(function(){
$('.time').each(function () {
const unix = parseInt($(this).text().trim()) + AppState.TIMEFIX;
$(this).text(moment.unix(unix).format('DD MMM YYYY HH:mm:ss'));
});
let coords
setTimeout(async() => {
map.invalidateSize(); // force Leaflet to recalc
map.fitBounds(polyline.getBounds());
// map.fitBounds(await coords.map(c => L.latLng(c[0], c[1])));
}, 200);
const linesData = (@json($list));
// 1) Initialize map
const map = L.map("leafMap").setView([-8.90507, 125.9945732], 10)
// 2) Add tile layer
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 20,
crossOrigin: true
}).addTo(map);
// // // 3) Coordinates (Lat, Lng) for polyline
const points = linesData
.filter(p => p.latitude && p.longitude)
.map((point) => [point.latitude, point.longitude])
// 4) Add polyline
const polyline = L.polyline(points,{
color: 'red',
weight: 3,
opacity: 0.7,
smoothFactor: 1
})
// .addTo(map);
// const lines = L.Routing.control({
// waypoints: points,
// // router: L.Routing.osrmv1(),
// router: L.Routing.osrmv1({
// serviceUrl: "https://brilianapps.britimorleste.tl:5001/route/v1",
// }),
// show: false,
// itinerary: null, // 👈 completely removes the panel
// addWaypoints: false, // ❌ prevent adding points by clicking
// draggableWaypoints: false, // ❌ prevent dragging markers
// routeWhileDragging: false, // optional: dont reroute while dragging
// createMarker: () => null,
// routingOptions: {
// radiuses: [100, 100] // tolerance in meters per waypoint
// },
// lineOptions:{
// styles: [{ color: "#C0392B", weight: 4, opacity: 0.7 }],
// },
// }).addTo(map)
function chunkArray(arr, size) {
const result = [];
for (let i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, i + size));
}
return result;
}
function fetchOsrm(points) {
const coords = points;
const hints = ";".repeat(points.length - 1);
const body = {
coordinates: coords,
overview: "false",
alternatives: "false",
steps: "true",
hints: hints
};
let config = {
method: 'post',
maxBodyLength: Infinity,
url: 'https://brilianapps.britimorleste.tl/osrm-backend/post-route/v1/driving/',
headers: {
'Content-Type': 'application/json'
},
data: body
};
return axios.request(config)
.then((response) => {
return response.data;
})
.catch((error) => {
console.error("Error:", error.message);
return null;
});
}
function decodeOSRMGeometry(encoded) {
const coordinates = [];
let index = 0,
lat = 0,
lng = 0;
while (index < encoded.length) {
let result = 1,
shift = 0,
b;
do {
b = encoded.charCodeAt(index++) - 63 - 1;
result += b << shift;
shift += 5;
} while (b >= 0x1f);
lat += (result & 1 ? ~(result >> 1) : result >> 1);
result = 1;
shift = 0;
do {
b = encoded.charCodeAt(index++) - 63 - 1;
result += b << shift;
shift += 5;
} while (b >= 0x1f);
lng += (result & 1 ? ~(result >> 1) : result >> 1);
coordinates.push([lat / 1e5, lng / 1e5]);
}
return coordinates;
}
async function getCoordinates(points) {
const chunkSize = 500;
const chunks = chunkArray(points, chunkSize); // Split the points array into chunks of 500
let allCoords = [];
for (const chunk of chunks) {
const osrm = await fetchOsrm(chunk); // Fetch OSRM data for each chunk
if (!osrm) {
console.log("OSRM failed for chunk");
return;
}
const coords = osrm.routes[0].legs.flatMap(leg =>
leg.steps.flatMap(step =>
decodeOSRMGeometry(step.geometry)
)
);
allCoords = allCoords.concat(coords); // Combine the result
}
// Now add the polyline to the map
L.polyline(allCoords, {
color: "#2980B9",
weight: 3,
opacity: 0.8
}).addTo(map);
// map.fitBounds(allCoords.map(c => L.latLng(c[0], c[1])));
}
// Usage: Pass the array of coordinates to the function
getCoordinates(points);
// start and finish point
const startIcon = L.icon({
iconUrl: "{{ asset('images/start.png') }}",
iconSize: [30, 30],
iconAnchor: [15, 28], // lb, rt, bottom, rb. Positive
})
L.marker(points[0], {icon: startIcon}).addTo(map)
const finishIcon = L.icon({
iconUrl: "{{ asset('images/finish.png') }}",
iconSize: [30, 30],
iconAnchor: [15, 28], // lb, rt, bottom, rb. Positive
})
L.marker(points[points.length - 1], {icon: finishIcon}).addTo(map)
// // 5) Auto-fit map to polyline bounds
// map.fitBounds(polyline.getBounds())
// download pdf
window._downloadReportBound ||= (
$(document).on('click', '#btnDownloadReport', function () {
$('#viewPdf').printThis({
debug: false, // show the iframe for debugging
importCSS: true, // copy linked styles
importStyle: true, // copy inline styles
});
// const viewPdf = document.getElementById("viewPdf");
// // find overlay svg (the one holding polylines)
// const overlaySvg = document.querySelector('.leaflet-overlay-pane svg');
// let originalTransform = '';
// if (overlaySvg) {
// originalTransform = overlaySvg.style.transform;
// overlaySvg.style.transform = 'none';
// }
// html2canvas(viewPdf, {
// scale: 2,
// useCORS: true,
// logging: true
// }).then(canvas => {
// const imgData = canvas.toDataURL('image/png');
// const { jsPDF } = window.jspdf;
// const pdf = new jsPDF('p', 'mm', 'a4');
// const pageWidth = pdf.internal.pageSize.getWidth();
// const pageHeight = pdf.internal.pageSize.getHeight();
// const imgWidth = pageWidth - 20; // margin
// const imgHeight = canvas.height * imgWidth / canvas.width;
// let position = 10;
// // 👉 Handle multipage content
// let heightLeft = imgHeight;
// while (heightLeft > 0) {
// pdf.addImage(imgData, 'PNG', 10, position, imgWidth, imgHeight);
// heightLeft -= pageHeight;
// if (heightLeft > 0) {
// pdf.addPage();
// position = 0;
// }
// }
// pdf.save(`{{$nopol1}} Trip Report {{$start['time']}}.pdf`);
// });
}),
true
);
});
</script>

View File

@ -43,7 +43,7 @@
<div class="form-group">
<label class="text-muted">From</label>
<!-- default today -->
<!-- <input type="date" class="form-control" id="tgl0" value="{{ date('Y-m-d') }}"> -->
<!-- <input class="form-control" id="tgl0" value="20-08-2025 00:00"> -->
<input class="form-control" id="tgl0" value="{{ date('d-m-Y 00:00') }}">
</div>
</div>
@ -65,7 +65,7 @@
</div>
</div>
<div class="col-1 d-flex align-items-end">
<button class="btn btn-sm btn-primary" id="submitFilter">Submit</button>
<button class="btn btn-primary" id="submitFilter">Submit</button>
</div>
<div class="col-5 d-flex align-items-end">
<button class="btn btn-sm btn-danger ms-auto" id="btnDownloadReport">Download Report</button>
@ -78,8 +78,10 @@
<tr class="">
<!-- <th class="">Vehicle ID</th> -->
<!-- <th class="text-center">Action</th> -->
<th class="">Vehicle Name</th>
<!-- <th class="">Vehicle Name</th> -->
<th class="">License Plate Number</th>
<th class="">Dist. Cat.</th>
<th class="">Pool</th>
<th class="">Time</th>
<th class="">Speed (kph)</th>
<th class="">Location</th>
@ -116,8 +118,7 @@
activate: function() {
Wrapper.init();
Wrapper.event();
DTable.activate();
// DTable.activate();
},
init: ()=>{
$('#tgl0, #tgl1').datetimepicker({
@ -193,19 +194,27 @@
deferRender: true,
columns: [
// { data: 'id', visible: false }, // vhc_id
{
data: 'name',
className: 'text-start text-nowrap',
},
// {
// data: 'name',
// className: 'text-start text-nowrap',
// },
{
data: 'nopol1',
className: 'text-start',
},
{
data: 'dc_code',
className: 'text-start',
},
{
data: 'pool_code',
className: 'text-start',
},
{
data: "crt_d",
className: 'text-nowrap',
render: (data, type, row, meta) => {
return moment.unix(data).format('DD MMM YYYY HH:mm:ss');
return moment.unix(data + AppState.TIMEFIX).format('DD MMM YYYY HH:mm:ss');
}
},
{ data: "speed", className: 'text-end'},
@ -220,7 +229,13 @@
buttons: [
{
extend: 'excelHtml5',
title: `Abnormality Report - ${moment(safeVal('#tgl0')).format('DD MMM YYYY')} to ${moment(safeVal('#tgl1')).format('DD MMM YYYY')}`,
title: () => {
return `
Abnormality Trip Report -
${moment($('#tgl0').val(), "DD-MM-YYYY HH:mm").format('DD MMM YYYY HH:mm')}
to
${moment($('#tgl1').val(), "DD-MM-YYYY HH:mm").format('DD MMM YYYY HH:mm')}`
},
className: 'd-none', // hide default button
exportOptions: {
columns: ':visible:not(:first-child)' // 🔹 exclude first column

View File

@ -5,10 +5,16 @@
@extends('app.app')
@section('title')
Vehicles
Vehicles Trip Report
@endsection
@section('customcss')
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""
/>
@endsection
@section('content')
@ -43,13 +49,14 @@
<div class="form-group">
<label class="text-muted">From</label>
<!-- default today -->
<!-- <input type="date" class="form-control" id="tgl0" value="{{ date('Y-m-d') }}"> -->
<!-- <input class="form-control" id="tgl0" value="02-09-2025 00:00"> -->
<input class="form-control" id="tgl0" value="{{ date('d-m-Y 00:00') }}">
</div>
</div>
<div class="col-2">
<div class="form-group">
<label class="text-muted">To</label>
<!-- <input class="form-control" id="tgl1" value="02-09-2025 23:00"> -->
<input class="form-control" id="tgl1" value="{{ date('d-m-Y 23:59') }}">
</div>
</div>
@ -65,7 +72,7 @@
</div>
</div>
<div class="col-1 d-flex align-items-end">
<button class="btn btn-sm btn-primary" id="submitFilter">Submit</button>
<button class="btn btn-primary" id="submitFilter">Submit</button>
</div>
<div class="col-5 d-flex align-items-end">
<button class="btn btn-sm btn-danger ms-auto" id="btnDownloadReport">Download Report</button>
@ -76,16 +83,20 @@
<table id="tVehicleTrips" class="table table-hover dataTable w-100">
<thead>
<tr class="">
<th class="">Vehicle ID</th>
<!-- <th class="text-center">Action</th> -->
<th class="">Vehicle Name</th>
<th class="" hidden>Vehicle ID</th>
<th class="">License Plate Number</th>
<th class="">Dist. Cat.</th>
<th class="">Pool</th>
<th class="">Number of Trip</th>
<th class="">Total Mileage (km)</th>
<th class="">Trip #</th>
<th class="">Start</th>
<th class="">Finish</th>
<th class="">Mileage (km)</th>
<th class="">Duration</th>
<th class="">Start (km)</th>
<th class="">Finish (km)</th>
<th class="">Distance (km)</th>
<th class="">Detail</th>
</tr>
</thead>
<!-- <tbody>
@ -164,6 +175,20 @@
</div>
<!-- modal -->
<div class="modal fade" data-bs-backdrop="static" data-bs-keyboard="false" id="mdlDetailTrip" aria-labelledby="mdlDetailTripLabel" aria-hidden="true">
<!-- <div class="modal-dialog modal-dialog modal-dialog-centered modal-dialog-scrollable modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="mdlDetailTripLabel">-----</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
</div>
</div>
</div> -->
</div>
@endsection
@section('customjs')
@ -172,6 +197,14 @@
<script src="https://cdn.datatables.net/buttons/2.4.2/js/dataTables.buttons.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdn.datatables.net/buttons/2.4.2/js/buttons.html5.min.js"></script>
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js" integrity="sha512-ozq8xQKq6urvuU6jNgkfqAmT7jKN2XumbrX1JiB3TnF7tI48DPI4Gy1GXKD/V3EExgAs1V+pRO7vwtS1LHg0Gw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<script src="https://unpkg.com/leaflet-routing-machine@3.2.12/dist/leaflet-routing-machine.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="{{ asset('assets/vendor/printThis.js') }}"></script>
<script>
'use strict';
const State = {
@ -187,8 +220,8 @@
activate: function() {
Wrapper.init();
Wrapper.event();
DTable.activate();
// DTable.table = $('#tVehicleTrips').DataTable()
// DTable.activate();
},
init: ()=>{
$('#tgl0, #tgl1').datetimepicker({
@ -207,6 +240,21 @@
$('#btnDownloadReport').on('click', function() {
DTable.table.button('.buttons-excel').trigger();
});
// detail trip
$('#tVehicleTrips').on('click', '.btnDetailTrip', function(e) {
e.preventDefault();
const vid = $(this).data('vid');
const tgl0 = $(this).data('tgl0');
const tgl1 = $(this).data('tgl1');
const nopol1 = $(this).data('nopol1');
// $("#mdlDetailTrip").load("{{ route('view_report_trip_detail') }}");
$('#mdlDetailTrip').empty().load("{{ route('view_report_trip_detail') }}", `vid=${vid}&tgl0=${tgl0}&tgl1=${tgl1}&nopol1=${nopol1}`, () => {
$('#mdlDetailTrip').modal('show')
})
});
},
};
@ -227,10 +275,6 @@
return;
}
let mileageSum = {};
let tripCount = {};
DTable.table = $('#tVehicleTrips').DataTable({
searching: false, // 🔹 remove search box
ordering: false, // 🔹 disable sorting for all columns
@ -251,20 +295,10 @@
cptid=${AppState.current_company}
&from_date=${moment($('#tgl0').val(), "DD-MM-YYYY HH:mm").unix()}
&to_date=${moment($('#tgl1').val(), "DD-MM-YYYY HH:mm").unix()}
&vid=${$('#filterNopol').val() || ''}`,
&vid=${$('#filterNopol').val() || ''}
`,
type: 'GET',
success: function(json) {
// Precompute totals by vhc_id
json.data.forEach(row => {
const id = row.id; // vhc_id
if (!mileageSum[id]) {
mileageSum[id] = 0;
tripCount[id] = 0;
}
mileageSum[id] += parseFloat(row.milleage || 0);
tripCount[id] += 1;
});
callback(json);
},
error: function(xhr, status, error) {
@ -280,13 +314,6 @@
deferRender: true,
columns: [
{ data: 'id', visible: false }, // vhc_id
{
data: 'name',
className: 'text-start text-nowrap',
visible: true,
orderable: true,
searchable: true,
},
{
data: 'nopol1',
className: 'text-start',
@ -294,15 +321,17 @@
orderable: true,
searchable: true,
},
{ data: "id", className: 'text-end'},
{ data: "id", className: 'text-end'},
{ data: "dc_code", className: 'text-start'},
{ data: "pool_code", className: 'text-start'},
{ data: "total_trip", className: 'text-end'},
{ data: "total_mileage", className: 'text-end', render: (data, type, row, meta) => parseFloat(data).toFixed(2)},
{ data: "trip_id", className: 'text-end'},
{
data: 'start',
render: (data, type, row, meta) => {
// let addr = row
return `
${moment.unix(data).format('DD MMM YYYY HH:mm:ss')}<br>
${moment.unix(data + AppState.TIMEFIX).format('DD MMM YYYY HH:mm:ss')}<br>
${Helper.shortenText(decodeURIComponent(row.startLoc || 'address'), 255) || "-"}
`;
}
@ -312,15 +341,55 @@
render: (data, type, row, meta) => {
// let addr = row
return `
${moment.unix(data).format('DD MMM YYYY HH:mm:ss')}<br>
${moment.unix(data + AppState.TIMEFIX).format('DD MMM YYYY HH:mm:ss')}<br>
${Helper.shortenText(decodeURIComponent(row.finishLoc || 'address'), 255) || "-"}
`;
}
},
{
data: 'milleage',
className: 'text-start', render: function(data, type, row, meta) {
const start = moment.unix(row.start);
const finish = moment.unix(row.finish);
// calculate duration
const diff = moment.duration(finish.diff(start));
const hours = Math.floor(diff.asHours());
const minutes = diff.minutes();
return `${hours}h ${minutes}m`;
}
},
{
data: 'startMileage',
className: 'text-end', render: function(data, type, row, meta) {
return (data === null) ? '0' : parseFloat(data).toFixed(2);
return (data === null) ? '0' : parseFloat(data).toFixed(0);
}
},
{
data: 'finishMileage',
className: 'text-end', render: function(data, type, row, meta) {
return (data === null) ? '0' : parseFloat(data).toFixed(0);
}
},
{
data: 'mileage',
className: 'text-end', render: function(data, type, row, meta) {
const mileage = (row.finishMileage !== null && row.startMileage !== null) ? (row.finishMileage - row.startMileage) : row.mileage;
return (parseFloat(mileage).toFixed(2));
}
},
{
// visible: false,
data: 'vhc_id',
className: 'text-center',
render: function(data, type, row, meta) {
let action = `
<a href="#" class="text-decoration-none me-1 btnDetailTrip"
data-vid="${data}" data-tgl0="${row.start}" data-tgl1="${row.finish}" data-nopol1="${row.nopol1}">
<span class="icon ion-eye fz-16"></span>
</a>
`;
return action;
}
},
],
@ -347,16 +416,9 @@
$(rows).eq(i).find('td:eq(1)').attr('rowspan', rowspanCount);
$(rows).eq(i).find('td:eq(2)').attr('rowspan', rowspanCount);
$(rows).eq(i).find('td:eq(3)').attr('rowspan', rowspanCount);
$(rows).eq(i).find('td:eq(4)').attr('rowspan', rowspanCount);
// fill trip count
$(rows).eq(i).find('td:eq(2)')
.attr('rowspan', rowspanCount)
.text(tripCount[group]);
// fill mileage sum
$(rows).eq(i).find('td:eq(3)')
.attr('rowspan', rowspanCount)
.text(mileageSum[group].toFixed(2));
// $(rows).eq(i).find('td:eq(3)').attr('rowspan', rowspanCount);
last = group;
} else {
@ -365,16 +427,24 @@
$(rows).eq(i).find('td:eq(0)').remove();
$(rows).eq(i).find('td:eq(0)').remove();
$(rows).eq(i).find('td:eq(0)').remove();
$(rows).eq(i).find('td:eq(0)').remove();
// $(rows).eq(i).find('td:eq(0)').remove();
}
});
},
buttons: [
{
extend: 'excelHtml5',
title: `Vehicle Trip Report - ${moment(safeVal('#tgl0')).format('DD MMM YYYY')} to ${moment(safeVal('#tgl1')).format('DD MMM YYYY')}`,
title: () => {
return `
Vehicle Trip Report -
${moment($('#tgl0').val(), "DD-MM-YYYY HH:mm").format('DD MMM YYYY HH:mm')}
to
${moment($('#tgl1').val(), "DD-MM-YYYY HH:mm").format('DD MMM YYYY HH:mm')}`
},
className: 'd-none', // hide default button
exportOptions: {
columns: ':visible:not(:first-child)' // 🔹 exclude first column
columns: ':visible:not(:last-child)' //
}
}
]
@ -387,16 +457,6 @@
return (val === undefined || val === null || val === '') ? null : val;
}
const Filter = {
activate: function() {
Filter.event();
},
event: function() {},
triggerFilterCompany: function() {
DTable.reload();
},
}
async function convertImgHtmlToFile(imgHtml, imgWidth, imgHeight, mimeType, fileName) {
return new Promise((resolve, reject) => {
try {

View File

@ -77,7 +77,19 @@
@foreach ($permission as $module => $perms)
<div class="mb-3 row">
<label for="add-first_name" class="form-label text-capitalize col-3">
{{ $module == 'transaction' ? 'job' : ($module == 'client' ? 'company' : ($module == 'config_truck_type' ? 'Config Vehicle Type' : ($module == 'config_master_device' ? 'Config Master Devices' : ($module == 'config_logs_gps' ? 'Config Logs GPS' : $module)))) }}
{{
$module == 'transaction' ? 'job' :
($module == 'client' ? 'company' :
($module == 'config_truck_type' ? 'Config Vehicle Type' :
($module == 'config_master_device' ? 'Config Master Devices' :
($module == 'config_logs_gps' ? 'Config Logs GPS' :
($module == 'config_distribution_category' ? 'Config Distribution Category' :
($module == 'config_pool' ? 'Config Pool' :
($module == 'report_vehicle_trip' ? 'Report Vehicle Trip' :
($module == 'report_abnormality' ? 'Report Abnormality' :
($module == 'user_logs' ? 'Config User Activity' : $module
)))))))))
}}
</label>
<div class="col-9">
<div class="d-flex flex-wrap align-items-center gap-2">
@ -125,7 +137,19 @@
@foreach ($permission as $module => $perms)
<div class="mb-3 row">
<label for="add-first_name" class="form-label text-capitalize col-3">
{{ $module == 'transaction' ? 'job' : ($module == 'client' ? 'company' : ($module == 'config_truck_type' ? 'Config Vehicle Type' : ($module == 'config_master_device' ? 'Config Master Devices' : ($module == 'config_logs_gps' ? 'Config Logs GPS' : $module)))) }}
{{
$module == 'transaction' ? 'job' :
($module == 'client' ? 'company' :
($module == 'config_truck_type' ? 'Config Vehicle Type' :
($module == 'config_master_device' ? 'Config Master Devices' :
($module == 'config_logs_gps' ? 'Config Logs GPS' :
($module == 'config_distribution_category' ? 'Config Distribution Category' :
($module == 'config_pool' ? 'Config Pool' :
($module == 'report_vehicle_trip' ? 'Report Vehicle Trip' :
($module == 'report_abnormality' ? 'Report Abnormality' :
($module == 'user_logs' ? 'Config User Activity' : $module
)))))))))
}}
</label>
<div class="col-9">
<div class="d-flex flex-wrap align-items-center gap-2">

View File

@ -0,0 +1,263 @@
@php
$user_role = Auth::user()->role;
@endphp
@extends('app.app')
@section('title')
User Activity
@endsection
@section('customcss')
<link
rel="stylesheet"
href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""
/>
@endsection
@section('content')
<div class="container-fluid">
<div class="content">
<div class="card">
<div class="card-header">
<div class="row d-flex align-items-center">
<div class="col-3">
<p class="card-title text-bold mb-0">User Activity</p>
</div>
<div class="col-auto text-end ps-0">
<!-- <button class="btn btn-sm btn-danger">Download</button> -->
</div>
</div>
</div>
<div class="card-body">
<!-- filter -->
<div class="row">
<div class="col-2">
<div class="form-group">
<label class="text-muted">From</label>
<!-- default today -->
<!-- <input class="form-control" id="tgl0" value="02-09-2025 00:00"> -->
<input class="form-control" id="tgl0" value="{{ date('d-m-Y 00:00') }}">
</div>
</div>
<div class="col-2">
<div class="form-group">
<label class="text-muted">To</label>
<!-- <input class="form-control" id="tgl1" value="02-09-2025 23:00"> -->
<input class="form-control" id="tgl1" value="{{ date('d-m-Y 23:59') }}">
</div>
</div>
<div class="col-2">
<div class="form-group">
<label class="text-muted">User</label>
<select name="user" class="form-control" id="filterUser">
<option value="">-- All --</option>
@foreach ($users as $user)
<option value="{{ $user->id }}">{{ $user->email }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-1 d-flex align-items-end">
<button class="btn btn-primary" id="submitFilter">Submit</button>
</div>
<div class="col-5 d-flex align-items-end">
<button class="btn btn-sm btn-danger ms-auto" id="btnDownloadReport">Download Report</button>
</div>
</div>
</div>
<div class="table-responsive p-3">
<table id="tLogs" class="table table-hover dataTable w-100">
<thead>
<tr class="">
<th class="">Time</th>
<th class="">User</th>
<th class="">Module</th>
<th class="">Description</th>
<th class="">Action</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('customjs')
<script src="{{ asset('assets/js/load-image.all.min.js') }}"></script>
<!-- DataTables Buttons + JSZip (for Excel export) -->
<script src="https://cdn.datatables.net/buttons/2.4.2/js/dataTables.buttons.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
<script src="https://cdn.datatables.net/buttons/2.4.2/js/buttons.html5.min.js"></script>
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.js" integrity="sha512-ozq8xQKq6urvuU6jNgkfqAmT7jKN2XumbrX1JiB3TnF7tI48DPI4Gy1GXKD/V3EExgAs1V+pRO7vwtS1LHg0Gw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> -->
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin=""></script>
<script src="https://unpkg.com/leaflet-routing-machine@3.2.12/dist/leaflet-routing-machine.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="{{ asset('assets/vendor/printThis.js') }}"></script>
<script>
'use strict';
const Wrapper = {
activate: function() {
Wrapper.init();
Wrapper.event();
DTable.activate();
},
init: ()=>{
$('.time').each(function () {
const unix = parseInt($(this).text().trim()) + AppState.TIMEFIX;
$(this).text(moment.unix(unix).format('DD MMM YYYY HH:mm:ss'));
});
$('#tgl0, #tgl1').datetimepicker({
format:'d-m-Y H:i',
defaultTime:'00:00',
closeOnDateSelect: true,
// mask:true
});
},
event: function() {
$('#submitFilter').on('click', function() {
DTable.reload();
});
// Trigger export on external button click
$('#btnDownloadReport').on('click', function() {
DTable.table.button('.buttons-excel').trigger();
});
},
};
const DTable = {
table: null,
lastAjax: null, // keep track of the last ajax request
activate: function() {
DTable.reload();
},
reload: function() {
// Abort the last request if it's still running
if (DTable.lastAjax) {
DTable.lastAjax.abort();
}
if (DTable.table) {
// If table already exists → reload
DTable.table.ajax.reload();
return;
}
DTable.table = $('#tLogs').DataTable({
searching: false, // 🔹 remove search box
ordering: false, // 🔹 disable sorting for all columns
processing: true,
serverSide: false,
bLengthChange: true,
deferRender: true,
destroy: true,
ajax: function(data, callback, settings) {
// Abort previous
if (DTable.lastAjax) {
DTable.lastAjax.abort();
}
// Fire new request
DTable.lastAjax = $.ajax({
url: `{{ route('api_user_logs') }}?
tgl0=${moment($('#tgl0').val(), "DD-MM-YYYY HH:mm").unix()}
&tgl1=${moment($('#tgl1').val(), "DD-MM-YYYY HH:mm").unix()}
&userId=${$('#filterUser').val() || ''}
`,
type: 'GET',
success: function(json) {
callback(json);
},
error: function(xhr, status, error) {
if (status !== 'abort') {
console.error("AJAX error:", error);
}
},
complete: function() {
DTable.lastAjax = null;
}
});
},
deferRender: true,
columns: [
{
data: 'crt',
render: (data, type, row, meta) => {
// let addr = row
return `
${moment.unix(data).format('DD MMM YYYY HH:mm:ss')}<br>
`;
}
},
{ data: 'email' },
{
data: 'log',
render: (data, type, row, meta) => {
let log = JSON.parse(data);
let module = log.module || '-';
return module;
}
},
{
data: 'log',
render: (data, type, row, meta) => {
let log = JSON.parse(data);
let desc = log.desc || '-';
return desc;
}
},
{
data: 'log',
render: (data, type, row, meta) => {
let log = JSON.parse(data);
let action = log.action || '-';
// use badge for action
// action = `<span class="badge bg-secondary">${action}</span>`;
let badge = '';
if (action.toLowerCase() === 'create') {
badge = `<span class="badge bg-success">${action}</span>`;
} else if (action.toLowerCase() === 'update') {
badge = `<span class="badge bg-warning">${action}</span>`;
} else if (action.toLowerCase() === 'delete') {
badge = `<span class="badge bg-danger">${action}</span>`;
} else {
badge = `<span class="badge bg-secondary">${action}</span>`;
}
return badge;
}
},
],
paging: false,
buttons: [
{
extend: 'excelHtml5',
title: () => {
return `
User Activity Report -
${moment($('#tgl0').val(), "DD-MM-YYYY HH:mm").format('DD MMM YYYY HH:mm')}
to
${moment($('#tgl1').val(), "DD-MM-YYYY HH:mm").format('DD MMM YYYY HH:mm')}`
},
className: 'd-none', // hide default button
// exportOptions: {
// columns: ':visible:not(:last-child)' //
// }
}
]
});
},
};
Wrapper.activate();
</script>
@endsection

View File

@ -200,6 +200,13 @@
<label class="form-check-label" for="add-status"><span class="text-dark" id="add-txtStatus">Inactive</span></label>
</div>
</div>
<div class="mb-3">
<label for="add-status-sms" class="form-label">SMS Notification</label>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="add-status-sms">
<label class="form-check-label" for="add-status-sms"><span class="text-dark" id="add-txtStatusSms">Inactive</span></label>
</div>
</div>
</div>
</div>
<div id="add-group_track_vhc" class="row">
@ -382,6 +389,13 @@
<label class="form-check-label" for="updt-status"><span class="text-dark" id="updt-txtStatus">Inactive</span></label>
</div>
</div>
<div class="mb-3">
<label for="updt-status-sms" class="form-label">SMS Notification</label>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="updt-status-sms">
<label class="form-check-label" for="updt-status-sms"><span class="text-dark" id="updt-txtStatusSMS">Inactive</span></label>
</div>
</div>
</div>
</div>
</div>
@ -423,6 +437,11 @@
</div>
</div>
</div>
<div class="modal fade" data-bs-backdrop="static" data-bs-keyboard="false" id="mdlUserLogs" aria-labelledby="mdlUserLogsLabel" aria-hidden="true">
<!-- ajax -->
</div>
@endsection
@section('customjs')
@ -456,18 +475,20 @@
Filter.activate();
},
event: function() {
$('#add-status').bind('change', function() {
if ($(this).is(':checked'))
$('#add-txtStatus').html('Active')
else
$('#add-txtStatus').html('Inactive')
});
$('#updt-status').bind('change', function() {
if ($(this).is(':checked'))
$('#updt-txtStatus').html('Active')
else
$('#updt-txtStatus').html('Inactive')
$('#add-status, #add-status-sms, #updt-status, #updt-status-sms').bind('change', function() {
const labelSpan = $(this).closest('.form-check').find('span');
labelSpan.html($(this).is(':checked') ? 'Active' : 'Inactive');
// if ($(this).is(':checked'))
// $('#add-txtStatus').html('Active')
// else
// $('#add-txtStatus').html('Inactive')
});
// $('#updt-status').bind('change', function() {
// if ($(this).is(':checked'))
// $('#updt-txtStatus').html('Active')
// else
// $('#updt-txtStatus').html('Inactive')
// });
// $('#add-roles').on('change', function() {
// let role = $('#add-roles').val();
// if (role == State.user_roles.checker) {
@ -619,9 +640,13 @@
// <span class="icon ion-eye fz-16"></span>
// </a>
let action = `
<a href="#" class="text-decoration-none me-1 btnEdtUser" data-bs-toggle="tooltip"
<a href="#" class="text-decoration-none me-2 btnEdtUser" data-bs-toggle="tooltip"
data-bs-placement="bottom" title="Edit">
<span class="icon ion-eye fz-16"></span>
</a>
<a href="#" class="text-decoration-none me-2 btnLogUser" data-bs-toggle="tooltip" data-id="${row.id}"
data-bs-placement="bottom" title="Logs">
<span class="icon ion-android-laptop fz-16"></span>
</a>
`;
// <a href="#" class="text-decoration-none text-danger btnDelUser"
@ -781,6 +806,8 @@
data.status = State.user_status.inactive;
}
data.status_sms = ($('#add-status-sms').prop('checked')) ? 1 : 0;
data.is_tracking = $('#add-is_tracking').val();
data.vehicles = $('#add-vehicles').val();
return data;
@ -859,6 +886,26 @@
}
UUpdate.passDataToView(resp.data);
});
$('#tUsers').on('click', '.btnLogUser', async function(e) {
// let uid = $(e.target).closest('tr').find('td[data-id]').data('id');
// UUpdate.clearInput();
// let resp = await UUpdate.reqData({
// uid
// });
// if (resp.type != 'success') {
// Helper.toast('User Not Found', 'just now', 'please try again');
// return false;
// }
// UUpdate.passDataToView(resp.data);
e.preventDefault();
const id = $(this).data('id');
$('#mdlUserLogs').empty().load("{{ route('view_user_logs1') }}", `id=${id}`, () => {
$('#mdlUserLogs').modal('show')
})
});
$('#updtUserModal').on('shown.bs.modal', function() {
// initiate select2 if there
});
@ -920,6 +967,8 @@
});
},
passDataToView: function(data) {
console.log("data", data);
$('#updt-first_name').val(data.first_name);
$('#updt-fulladdress').val(data.fulladdress);
$('#updt-phone').val(data.phone);
@ -961,6 +1010,8 @@
$('#updtUserModal').data('id', data.id);
$('#updtUserModal').modal('show');
$('#updt-status-sms').prop('checked', (data.status_sms == 1) ? true : false).trigger('change');
},
getData: function() {
let data = {};
@ -996,6 +1047,8 @@
data.is_tracking = $('#updt-is_tracking').val();
data.vehicles = $('#updt-vehicles').val();
data.status_sms = ($('#updt-status-sms').prop('checked')) ? 1 : 0;
return data;
},
submitData: async function(data) {

View File

@ -147,6 +147,30 @@
<input type="number" id="add-mileage" class="form-control">
</div>
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="mb-0">
<label for="add-dc_code" class="col-form-label">Distribution Category:</label>
<select id="add-dc_code" class="form-control" style="width:100%;">
<option value="">Choose</option>
@foreach ($listDistribution as $distribution)
<option value="{{ $distribution->dc_code }}">{{ $distribution->dc_code }} - {{ $distribution->dc_name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-sm-6">
<div class="mb-0">
<label for="add-pool_code" class="col-form-label">Pool:</label>
<select id="add-pool_code" class="form-control" style="width:100%;">
<option value="">Choose</option>
@foreach ($listPool as $pool)
<option value="{{ $pool->pool_code }}">{{ $pool->pool_code }} - {{ $pool->pool_name }}</option>
@endforeach
</select>
</div>
</div>
</div>
</div>
</div>
@ -365,6 +389,31 @@
</div>
</div>
<div class="row">
<div class="col-sm-6">
<div class="mb-0">
<label for="edt-dc_code" class="col-form-label">Distribution Category:</label>
<select id="edt-dc_code" class="form-control" style="width:100%;">
<option value="">Choose</option>
@foreach ($listDistribution as $distribution)
<option value="{{ $distribution->dc_code }}">{{ $distribution->dc_code }} - {{ $distribution->dc_name }}</option>
@endforeach
</select>
</div>
</div>
<div class="col-sm-6">
<div class="mb-0">
<label for="edt-pool_code" class="col-form-label">Pool:</label>
<select id="edt-pool_code" class="form-control" style="width:100%;">
<option value="">Choose</option>
@foreach ($listPool as $pool)
<option value="{{ $pool->pool_code }}">{{ $pool->pool_code }} - {{ $pool->pool_name }}</option>
@endforeach
</select>
</div>
</div>
</div>
</div>
</div>
</div>
@ -1044,6 +1093,9 @@
data.append('vendor_id', safeVal('#add-vendor_id'));
data.append('dc_code', safeVal('#add-dc_code'));
data.append('pool_code', safeVal('#add-pool_code'));
// File input (cek ada file atau tidak)
const fvhcFile = $('#add-fvhc-file')[0].files[0];
if (fvhcFile) data.append('fvhc_file', fvhcFile);
@ -1265,6 +1317,9 @@
$('#mdlEdtVhc').data('id', data.vid);
$('#mdlEdtVhc').modal('show');
$('#edt-dc_code').val(data?.dc_code).trigger('change');
$('#edt-pool_code').val(data?.pool_code).trigger('change');
},
removeOptionDevice: function(type) {
$(`#edt-deviceid option[data-type='${type}']`).remove();
@ -1314,6 +1369,9 @@
// data.kir_exp = $('#edt-kirexp').val().split('-').reverse().join('-');
data.vendor_id = $('#edt-vendor_id').val();
data.dc_code = $('#edt-dc_code').val();
data.pool_code = $('#edt-pool_code').val();
return data;
},
submitData: async function(data) {

View File

@ -42,22 +42,33 @@
</a>
</li>
@endcan
@if (auth()->user()->can('config_truck_type.view') || auth()->user()->can('config_master_device.view') || auth()->user()->can('config_logs_gps.view'))
@if (auth()->user()->can('report_vehicle_trip.view') || auth()->user()->can('report_abnormality.view'))
<li class="nav-item dropdown {{ Request::segment(1) == 'reports' ? 'active' : '' }}">
<a class="nav-link dropdown-toggle" href="#" id="dropdownConfig" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Reports
</a>
<ul class="dropdown-menu" style="right: 0; left: auto;" aria-labelledby="dropdownConfig">
@can('report_vehicle_trip.view')
<li>
<a class="dropdown-item {{ Request::segment(2) == 'vehicle-trips' ? 'active' : '' }}" href="{{ route('view_report_vehicle_trips') }}" title="">Vehicle Trips</a>
</li>
@endcan
@can('report_abnormality.view')
<li>
<a class="dropdown-item {{ Request::segment(2) == 'abnormalities' ? 'active' : '' }}" href="{{ route('view_report_abnormalities') }}" title="">Abnormalities</a>
</li>
@endcan
</ul>
</li>
@endif
@if (auth()->user()->can('config_truck_type.view') || auth()->user()->can('config_master_device.view') || auth()->user()->can('config_logs_gps.view'))
@if (
auth()->user()->can('config_truck_type.view') ||
auth()->user()->can('config_master_device.view') ||
auth()->user()->can('config_logs_gps.view') ||
auth()->user()->can('config_distribution_category.view') ||
auth()->user()->can('config_pool.view') ||
auth()->user()->can('user_logs.view')
)
<li class="nav-item dropdown {{ Request::segment(1) == 'config' ? 'active' : '' }}">
<a class="nav-link dropdown-toggle" href="#" id="dropdownConfig" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Configuration
@ -78,6 +89,27 @@
<a class="dropdown-item {{ Request::segment(2) == 'logs_gps' ? 'active' : '' }}" href="{{ route('view_config_logs_gps') }}" title="">Logs Gps</a>
</li>
@endcan
@can('config_distribution_category.view')
<li>
<a class="dropdown-item {{ Request::segment(2) == 'distribution_category' ? 'active' : '' }}" href="{{ route('view_config_distribution_category') }}" title="">Distribution Category</a>
</li>
@endcan
@can('config_pool.view')
<li>
<a class="dropdown-item {{ Request::segment(2) == 'pool' ? 'active' : '' }}" href="{{ route('view_config_pool') }}" title="">Pool</a>
</li>
@endcan
@can('user_logs.view')
<li>
<a class="dropdown-item {{ Request::segment(1) == 'user_logs' ? 'active' : '' }}" href="{{ route('view_user_logs') }}" title="">User Activity</a>
</li>
<!-- <li class="nav-item {{ Request::segment(1) == 'user_logs' ? 'active' : '' }}">
<a class="nav-link d-flex align-items-center text-capitalize" aria-current="page" href="{{ route('view_user_logs') }}">
User Logs
</a>
</li> -->
@endcan
</ul>
</li>
@endif

View File

@ -120,6 +120,11 @@ Route::middleware(["auth", "auth.user"])->group(function () {
Route::get("/users", "UsersController@view_users")
->name("view_users")
->middleware("permission:user.view");
Route::get("/users/logs", "UsersController@view_user_logs1")->name("view_user_logs1");
Route::get("/api/users/logs", "UsersController@api_user_logs1")->name("api_user_logs1");
Route::get("/user-logs", "UserLogsController@view_user_logs")->name("view_user_logs");
Route::get("/api/user-logs", "UserLogsController@api_user_logs")->name("api_user_logs");
Route::get("/roles", "RolesController@view")
->name("view_roles")
@ -153,9 +158,29 @@ Route::middleware(["auth", "auth.user"])->group(function () {
);
Route::get("/config/logbook_keys", "LogbookKeysController@view_lgb_keys")->name("view_config_lgb_keys");
// config distribution category
Route::get("/config/distribution_category", "ConfDistributionController@view_distribution_category")
->name("view_config_distribution_category")
->middleware("permission:config_distribution_category.view");
Route::post("/api/conf/distribution_category", "ConfDistributionController@api_add_distribution_category")->name("api_conf_add_distribution_category");
Route::get("/api/conf/distribution_category", "ConfDistributionController@api_list_distribution_category")->name("api_conf_list_distribution_category");
Route::get("/api/conf/distribution_category/{id}", "ConfDistributionController@api_show_distribution_category")->name("api_conf_show_distribution_category");
Route::delete("/api/conf/distribution_category/{id}", "ConfDistributionController@api_del_distribution_category")->name("api_conf_del_distribution_category");
// config pool
Route::get("/config/pool", "ConfPoolController@view_pool")
->name("view_config_pool")
->middleware("permission:config_pool.view");
Route::post("/api/conf/pool", "ConfPoolController@api_add_pool")->name("api_conf_add_pool");
Route::get("/api/conf/pool", "ConfPoolController@api_list_pool")->name("api_conf_list_pool");
Route::get("/api/conf/pool/{id}", "ConfPoolController@api_show_pool")->name("api_conf_show_pool");
Route::delete("/api/conf/pool/{id}", "ConfPoolController@api_del_pool")->name("api_conf_del_pool")->middleware("permission:config_pool.delete");
// reports
Route::get("/reports/vehicle-trips", "ReportsController@view_report_vehicle_trips")->name("view_report_vehicle_trips");
Route::get("/reports/vehicle-trips-list", "ReportsController@api_report_vehicle_trips_list")->name("api_report_vehicle_trips_list");
Route::get("/reports/vehicle-trip-detail", "ReportsController@view_report_trip_detail")->name("view_report_trip_detail");
Route::get("/reports/abnormalities", "ReportsController@view_report_abnormalities")->name("view_report_abnormalities");
Route::get("/reports/abnormalities-list", "ReportsController@api_report_abnormalities_list")->name("api_report_abnormalities_list");