done revisi
This commit is contained in:
parent
25d77a8875
commit
4c335c9481
37
app/Http/Controllers/AuthController.php
Normal file
37
app/Http/Controllers/AuthController.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class AuthController extends Controller
|
||||
{
|
||||
public function login(){
|
||||
return view('auth.login');
|
||||
}
|
||||
|
||||
public function submitLogin(Request $request){
|
||||
$request->validate([
|
||||
'namauser' => 'required',
|
||||
'password' => 'required'
|
||||
]);
|
||||
$user = User::where('namauser', $request->namauser)->first();
|
||||
|
||||
if ($user && $user->passcode === sha1($request->password)) {
|
||||
auth()->login($user);
|
||||
$request->session()->regenerate();
|
||||
return redirect()->intended('/');
|
||||
}
|
||||
return back()->with(['error' => 'Gagal Login! Password / Username Salah']);
|
||||
|
||||
}
|
||||
|
||||
public function logout(){
|
||||
Auth::logout();
|
||||
request()->session()->invalidate();
|
||||
request()->session()->regenerateToken();
|
||||
return redirect('/login');
|
||||
}
|
||||
}
|
||||
@ -3,54 +3,96 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use App\Models\Karyawan;
|
||||
use App\Models\KaryawanLuar;
|
||||
|
||||
class KaryawanController extends Controller
|
||||
{
|
||||
public function listData(Request $request){
|
||||
$search = trim($request->input('search'));
|
||||
$page = (int) $request->input('page', 1);
|
||||
$perPage = (int) $request->input('per_page', 10);
|
||||
|
||||
if ($page < 1) $page = 1;
|
||||
if ($perPage < 1) $perPage = 10;
|
||||
if ($perPage > 50) $perPage = 50;
|
||||
$limit = (int) $request->input('limit', 50);
|
||||
if ($limit < 1) $limit = 50;
|
||||
if ($limit > 100) $limit = 100;
|
||||
|
||||
$validator = Validator::make(
|
||||
['search' => $search, 'page' => $page, 'per_page' => $perPage],
|
||||
['search' => 'required|min:2', 'page' => 'integer|min:1', 'per_page' => 'integer|min:1|max:50']
|
||||
['search' => $search, 'limit' => $limit],
|
||||
['search' => 'required|min:2', 'limit' => 'integer|min:1|max:100']
|
||||
);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json(['error' => 0, 'data' => [], 'meta' => [
|
||||
'page' => $page,
|
||||
'per_page' => $perPage,
|
||||
'total' => 0,
|
||||
'last_page' => 1,
|
||||
]]);
|
||||
return response()->json(['error' => 0, 'data' => []]);
|
||||
}
|
||||
|
||||
$paginator = Karyawan::where('statusenabled', true)->where('kedudukanfk', 1)
|
||||
->when($search, function($q) use ($search) {
|
||||
$q->where(function($q2) use ($search){
|
||||
$q2->where('namalengkap', 'ILIKE', "%$search%")
|
||||
->orWhere('nip_pns', 'ILIKE', "%$search%");
|
||||
});
|
||||
$rows = DB::connection('pgsql')
|
||||
->table('public.pegawai_m as pg')
|
||||
->leftJoin('public.mappegawaijabatantounitkerja_m as mp', function ($join) {
|
||||
$join->on('mp.objectpegawaifk', '=', 'pg.id')
|
||||
->where('mp.statusenabled', true)
|
||||
->where('mp.isprimary', true);
|
||||
})
|
||||
->select('id', 'namalengkap')
|
||||
->orderBy('namalengkap')
|
||||
->paginate($perPage, ['*'], 'page', $page);
|
||||
->leftJoin('public.unitkerjapegawai_m as ukp', function ($join) {
|
||||
$join->on('ukp.id', '=', 'mp.objectunitkerjapegawaifk')
|
||||
->where('ukp.statusenabled', true);
|
||||
})
|
||||
->where('pg.statusenabled', true)
|
||||
->where('pg.kedudukanfk', 1)
|
||||
->where(function ($q) use ($search) {
|
||||
$q->where('pg.namalengkap', 'ILIKE', "%$search%")
|
||||
->orWhere('pg.nip_pns', 'ILIKE', "%$search%");
|
||||
})
|
||||
->select([
|
||||
'pg.id',
|
||||
DB::raw("coalesce(pg.namalengkap, '-') as namalengkap"),
|
||||
DB::raw("coalesce(pg.nip_pns, '-') as nip_pns"),
|
||||
DB::raw("coalesce(ukp.name, '-') as unit_name"),
|
||||
])
|
||||
->orderBy('pg.namalengkap')
|
||||
->limit($limit)
|
||||
->get();
|
||||
|
||||
return response()->json([
|
||||
'error' => 0,
|
||||
'data' => $paginator->items(),
|
||||
'meta' => [
|
||||
'page' => $paginator->currentPage(),
|
||||
'per_page' => $paginator->perPage(),
|
||||
'total' => $paginator->total(),
|
||||
'last_page' => $paginator->lastPage(),
|
||||
],
|
||||
'data' => $rows,
|
||||
]);
|
||||
}
|
||||
|
||||
public function listDataKaryawanLuar(Request $request)
|
||||
{
|
||||
$search = trim((string) $request->input('search'));
|
||||
$limit = (int) $request->input('limit', 50);
|
||||
if ($limit < 1) $limit = 50;
|
||||
if ($limit > 100) $limit = 100;
|
||||
|
||||
$validator = Validator::make(
|
||||
['search' => $search, 'limit' => $limit],
|
||||
['search' => 'required|min:2', 'limit' => 'integer|min:1|max:100']
|
||||
);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json(['error' => 0, 'data' => []]);
|
||||
}
|
||||
|
||||
$data = KaryawanLuar::query()
|
||||
->from('public.pegawai_luar_pl as pl')
|
||||
->where(function($q) use($search){
|
||||
$q->where('pl.nama', 'ILIKE', "%$search%")
|
||||
->orWhere('pl.nik', 'ILIKE', "%$search%");
|
||||
})
|
||||
->select([
|
||||
'pl.id as id',
|
||||
DB::raw("coalesce(pl.nama, '-') as namalengkap"),
|
||||
DB::raw("coalesce(pl.nik, '-') as nip_pns"),
|
||||
DB::raw("coalesce(nullif(pl.tipe, ''), '-') as unit_name"),
|
||||
])
|
||||
->orderBy('pl.nama')
|
||||
->limit($limit)
|
||||
->get();
|
||||
return response()->json([
|
||||
'error' => 0,
|
||||
'data' => $data
|
||||
]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
145
app/Http/Controllers/MasterPitStopController.php
Normal file
145
app/Http/Controllers/MasterPitStopController.php
Normal file
@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\MasterPitStopPraAkre;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class MasterPitStopController extends Controller
|
||||
{
|
||||
private function dtInt($value, $default = 0)
|
||||
{
|
||||
$n = is_numeric($value) ? (int) $value : (int) $default;
|
||||
return $n < 0 ? (int) $default : $n;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
return view('master_pitstop.index', [
|
||||
'title' => 'Master PitStop',
|
||||
]);
|
||||
}
|
||||
|
||||
public function data(Request $request)
|
||||
{
|
||||
$draw = $this->dtInt($request->input('draw'), 1);
|
||||
$start = $this->dtInt($request->input('start'), 0);
|
||||
$length = $this->dtInt($request->input('length'), 10);
|
||||
if ($length < 1) $length = 10;
|
||||
if ($length > 200) $length = 200;
|
||||
|
||||
$search = trim((string) data_get($request->all(), 'search.value', ''));
|
||||
|
||||
$baseNoSearch = MasterPitStopPraAkre::query()
|
||||
->select(['id', 'nama', 'statusenabled'])
|
||||
->orderBy('id');
|
||||
|
||||
$recordsTotal = (clone $baseNoSearch)->count();
|
||||
|
||||
$base = clone $baseNoSearch;
|
||||
if ($search !== '') {
|
||||
$base->where('nama', 'ILIKE', '%' . $search . '%');
|
||||
}
|
||||
|
||||
$recordsFiltered = $search === '' ? $recordsTotal : (clone $base)->count();
|
||||
|
||||
$rows = $base
|
||||
->orderBy('nama')
|
||||
->offset($start)
|
||||
->limit($length)
|
||||
->get();
|
||||
|
||||
$data = $rows->map(function ($r) {
|
||||
return [
|
||||
'id' => (int) $r->id,
|
||||
'nama' => (string) ($r->nama ?? '-'),
|
||||
'statusenabled' => (bool) $r->statusenabled,
|
||||
];
|
||||
})->values();
|
||||
|
||||
return response()->json([
|
||||
'draw' => $draw,
|
||||
'recordsTotal' => $recordsTotal,
|
||||
'recordsFiltered' => $recordsFiltered,
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'nama' => 'required|string|max:255',
|
||||
'statusenabled' => 'nullable|boolean',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'error' => 1,
|
||||
'message' => 'Validasi gagal.',
|
||||
'errors' => $validator->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$payload = $validator->validated();
|
||||
|
||||
$row = MasterPitStopPraAkre::create([
|
||||
'nama' => trim($payload['nama']),
|
||||
'statusenabled' => array_key_exists('statusenabled', $payload) ? (bool) $payload['statusenabled'] : true,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'error' => 0,
|
||||
'message' => 'Berhasil menambah master pitstop.',
|
||||
'data' => [
|
||||
'id' => $row->id,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$row = MasterPitStopPraAkre::findOrFail($id);
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'nama' => 'required|string|max:255',
|
||||
'statusenabled' => 'nullable'
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'error' => 1,
|
||||
'message' => 'Validasi gagal.',
|
||||
'errors' => $validator->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$payload = $validator->validated();
|
||||
|
||||
$row->update([
|
||||
'nama' => trim($payload['nama']),
|
||||
'statusenabled' => trim($payload['statusenabled'])
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'error' => 0,
|
||||
'message' => 'Berhasil mengubah master pitstop.',
|
||||
]);
|
||||
}
|
||||
|
||||
public function toggle(Request $request, $id)
|
||||
{
|
||||
$row = MasterPitStopPraAkre::findOrFail($id);
|
||||
$row->statusenabled = !$row->statusenabled;
|
||||
$row->save();
|
||||
|
||||
return response()->json([
|
||||
'error' => 0,
|
||||
'message' => $row->statusenabled ? 'Berhasil mengaktifkan.' : 'Berhasil menonaktifkan.',
|
||||
'data' => [
|
||||
'statusenabled' => (bool) $row->statusenabled,
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
16
app/Models/KaryawanLuar.php
Normal file
16
app/Models/KaryawanLuar.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class KaryawanLuar extends Model
|
||||
{
|
||||
protected $connection = 'pgsql';
|
||||
protected $table = 'public.pegawai_luar_pl';
|
||||
public $timestamps = false;
|
||||
protected $primaryKey = "id";
|
||||
protected $guarded = [
|
||||
'id',
|
||||
];
|
||||
}
|
||||
@ -16,32 +16,11 @@ class User extends Authenticatable
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
protected $connection = 'pgsql';
|
||||
protected $table = 'public.loginuser_s';
|
||||
public $timestamps = false;
|
||||
protected $primaryKey = "id";
|
||||
protected $guarded = [
|
||||
'id',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,8 +6,10 @@
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^8.2",
|
||||
"barryvdh/laravel-dompdf": "2.2",
|
||||
"laravel/framework": "^11.0",
|
||||
"laravel/tinker": "^2.9"
|
||||
"laravel/tinker": "^2.9",
|
||||
"phpoffice/phpspreadsheet": "1.29"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.23",
|
||||
|
||||
715
composer.lock
generated
715
composer.lock
generated
@ -4,8 +4,85 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "e5fbbe6137dc77bcd5b6646b5dd75b52",
|
||||
"content-hash": "c59ab79e8eb552439286f7df9c02a9c5",
|
||||
"packages": [
|
||||
{
|
||||
"name": "barryvdh/laravel-dompdf",
|
||||
"version": "v2.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/barryvdh/laravel-dompdf.git",
|
||||
"reference": "c96f90c97666cebec154ca1ffb67afed372114d8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/c96f90c97666cebec154ca1ffb67afed372114d8",
|
||||
"reference": "c96f90c97666cebec154ca1ffb67afed372114d8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"dompdf/dompdf": "^2.0.7",
|
||||
"illuminate/support": "^6|^7|^8|^9|^10|^11",
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"larastan/larastan": "^1.0|^2.7.0",
|
||||
"orchestra/testbench": "^4|^5|^6|^7|^8|^9",
|
||||
"phpro/grumphp": "^1 || ^2.5",
|
||||
"squizlabs/php_codesniffer": "^3.5"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"aliases": {
|
||||
"PDF": "Barryvdh\\DomPDF\\Facade\\Pdf",
|
||||
"Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf"
|
||||
},
|
||||
"providers": [
|
||||
"Barryvdh\\DomPDF\\ServiceProvider"
|
||||
]
|
||||
},
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Barryvdh\\DomPDF\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Barry vd. Heuvel",
|
||||
"email": "barryvdh@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A DOMPDF Wrapper for Laravel",
|
||||
"keywords": [
|
||||
"dompdf",
|
||||
"laravel",
|
||||
"pdf"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/barryvdh/laravel-dompdf/issues",
|
||||
"source": "https://github.com/barryvdh/laravel-dompdf/tree/v2.2.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://fruitcake.nl",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/barryvdh",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-04-25T13:16:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "brick/math",
|
||||
"version": "0.14.8",
|
||||
@ -377,6 +454,68 @@
|
||||
],
|
||||
"time": "2024-02-05T11:56:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dompdf/dompdf",
|
||||
"version": "v2.0.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dompdf/dompdf.git",
|
||||
"reference": "c20247574601700e1f7c8dab39310fca1964dc52"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dompdf/dompdf/zipball/c20247574601700e1f7c8dab39310fca1964dc52",
|
||||
"reference": "c20247574601700e1f7c8dab39310fca1964dc52",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"ext-mbstring": "*",
|
||||
"masterminds/html5": "^2.0",
|
||||
"phenx/php-font-lib": ">=0.5.4 <1.0.0",
|
||||
"phenx/php-svg-lib": ">=0.5.2 <1.0.0",
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-json": "*",
|
||||
"ext-zip": "*",
|
||||
"mockery/mockery": "^1.3",
|
||||
"phpunit/phpunit": "^7.5 || ^8 || ^9",
|
||||
"squizlabs/php_codesniffer": "^3.5"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-gd": "Needed to process images",
|
||||
"ext-gmagick": "Improves image processing performance",
|
||||
"ext-imagick": "Improves image processing performance",
|
||||
"ext-zlib": "Needed for pdf stream compression"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Dompdf\\": "src/"
|
||||
},
|
||||
"classmap": [
|
||||
"lib/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-2.1"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "The Dompdf Community",
|
||||
"homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md"
|
||||
}
|
||||
],
|
||||
"description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
|
||||
"homepage": "https://github.com/dompdf/dompdf",
|
||||
"support": {
|
||||
"issues": "https://github.com/dompdf/dompdf/issues",
|
||||
"source": "https://github.com/dompdf/dompdf/tree/v2.0.8"
|
||||
},
|
||||
"time": "2024-04-29T13:06:17+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dragonmantank/cron-expression",
|
||||
"version": "v3.6.0",
|
||||
@ -508,6 +647,67 @@
|
||||
],
|
||||
"time": "2025-03-06T22:45:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ezyang/htmlpurifier",
|
||||
"version": "v4.19.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ezyang/htmlpurifier.git",
|
||||
"reference": "b287d2a16aceffbf6e0295559b39662612b77fcf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/b287d2a16aceffbf6e0295559b39662612b77fcf",
|
||||
"reference": "b287d2a16aceffbf6e0295559b39662612b77fcf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "~5.6.0 || ~7.0.0 || ~7.1.0 || ~7.2.0 || ~7.3.0 || ~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"cerdic/css-tidy": "^1.7 || ^2.0",
|
||||
"simpletest/simpletest": "dev-master"
|
||||
},
|
||||
"suggest": {
|
||||
"cerdic/css-tidy": "If you want to use the filter 'Filter.ExtractStyleBlocks'.",
|
||||
"ext-bcmath": "Used for unit conversion and imagecrash protection",
|
||||
"ext-iconv": "Converts text to and from non-UTF-8 encodings",
|
||||
"ext-tidy": "Used for pretty-printing HTML"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"library/HTMLPurifier.composer.php"
|
||||
],
|
||||
"psr-0": {
|
||||
"HTMLPurifier": "library/"
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/library/HTMLPurifier/Language/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-2.1-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Edward Z. Yang",
|
||||
"email": "admin@htmlpurifier.org",
|
||||
"homepage": "http://ezyang.com"
|
||||
}
|
||||
],
|
||||
"description": "Standards compliant HTML filter written in PHP",
|
||||
"homepage": "http://htmlpurifier.org/",
|
||||
"keywords": [
|
||||
"html"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/ezyang/htmlpurifier/issues",
|
||||
"source": "https://github.com/ezyang/htmlpurifier/tree/v4.19.0"
|
||||
},
|
||||
"time": "2025-10-17T16:34:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fruitcake/php-cors",
|
||||
"version": "v1.4.0",
|
||||
@ -2013,6 +2213,258 @@
|
||||
],
|
||||
"time": "2026-03-08T20:05:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "maennchen/zipstream-php",
|
||||
"version": "3.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/maennchen/ZipStream-PHP.git",
|
||||
"reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/maennchen/ZipStream-PHP/zipball/aeadcf5c412332eb426c0f9b4485f6accba2a99f",
|
||||
"reference": "aeadcf5c412332eb426c0f9b4485f6accba2a99f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"ext-zlib": "*",
|
||||
"php-64bit": "^8.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"brianium/paratest": "^7.7",
|
||||
"ext-zip": "*",
|
||||
"friendsofphp/php-cs-fixer": "^3.16",
|
||||
"guzzlehttp/guzzle": "^7.5",
|
||||
"mikey179/vfsstream": "^1.6",
|
||||
"php-coveralls/php-coveralls": "^2.5",
|
||||
"phpunit/phpunit": "^11.0",
|
||||
"vimeo/psalm": "^6.0"
|
||||
},
|
||||
"suggest": {
|
||||
"guzzlehttp/psr7": "^2.4",
|
||||
"psr/http-message": "^2.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"ZipStream\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paul Duncan",
|
||||
"email": "pabs@pablotron.org"
|
||||
},
|
||||
{
|
||||
"name": "Jonatan Männchen",
|
||||
"email": "jonatan@maennchen.ch"
|
||||
},
|
||||
{
|
||||
"name": "Jesse Donat",
|
||||
"email": "donatj@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "András Kolesár",
|
||||
"email": "kolesar@kolesar.hu"
|
||||
}
|
||||
],
|
||||
"description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.",
|
||||
"keywords": [
|
||||
"stream",
|
||||
"zip"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/maennchen/ZipStream-PHP/issues",
|
||||
"source": "https://github.com/maennchen/ZipStream-PHP/tree/3.1.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/maennchen",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-01-27T12:07:53+00:00"
|
||||
},
|
||||
{
|
||||
"name": "markbaker/complex",
|
||||
"version": "3.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/MarkBaker/PHPComplex.git",
|
||||
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/MarkBaker/PHPComplex/zipball/95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
|
||||
"reference": "95c56caa1cf5c766ad6d65b6344b807c1e8405b9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
|
||||
"phpcompatibility/php-compatibility": "^9.3",
|
||||
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
|
||||
"squizlabs/php_codesniffer": "^3.7"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Complex\\": "classes/src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Mark Baker",
|
||||
"email": "mark@lange.demon.co.uk"
|
||||
}
|
||||
],
|
||||
"description": "PHP Class for working with complex numbers",
|
||||
"homepage": "https://github.com/MarkBaker/PHPComplex",
|
||||
"keywords": [
|
||||
"complex",
|
||||
"mathematics"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/MarkBaker/PHPComplex/issues",
|
||||
"source": "https://github.com/MarkBaker/PHPComplex/tree/3.0.2"
|
||||
},
|
||||
"time": "2022-12-06T16:21:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "markbaker/matrix",
|
||||
"version": "3.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/MarkBaker/PHPMatrix.git",
|
||||
"reference": "728434227fe21be27ff6d86621a1b13107a2562c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/MarkBaker/PHPMatrix/zipball/728434227fe21be27ff6d86621a1b13107a2562c",
|
||||
"reference": "728434227fe21be27ff6d86621a1b13107a2562c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "dev-master",
|
||||
"phpcompatibility/php-compatibility": "^9.3",
|
||||
"phpdocumentor/phpdocumentor": "2.*",
|
||||
"phploc/phploc": "^4.0",
|
||||
"phpmd/phpmd": "2.*",
|
||||
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0",
|
||||
"sebastian/phpcpd": "^4.0",
|
||||
"squizlabs/php_codesniffer": "^3.7"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Matrix\\": "classes/src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Mark Baker",
|
||||
"email": "mark@demon-angel.eu"
|
||||
}
|
||||
],
|
||||
"description": "PHP Class for working with matrices",
|
||||
"homepage": "https://github.com/MarkBaker/PHPMatrix",
|
||||
"keywords": [
|
||||
"mathematics",
|
||||
"matrix",
|
||||
"vector"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/MarkBaker/PHPMatrix/issues",
|
||||
"source": "https://github.com/MarkBaker/PHPMatrix/tree/3.0.1"
|
||||
},
|
||||
"time": "2022-12-02T22:17:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "masterminds/html5",
|
||||
"version": "2.10.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Masterminds/html5-php.git",
|
||||
"reference": "fcf91eb64359852f00d921887b219479b4f21251"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251",
|
||||
"reference": "fcf91eb64359852f00d921887b219479b4f21251",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.7-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Masterminds\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Matt Butcher",
|
||||
"email": "technosophos@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Matt Farina",
|
||||
"email": "matt@mattfarina.com"
|
||||
},
|
||||
{
|
||||
"name": "Asmir Mustafic",
|
||||
"email": "goetas@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "An HTML5 parser and serializer.",
|
||||
"homepage": "http://masterminds.github.io/html5-php",
|
||||
"keywords": [
|
||||
"HTML5",
|
||||
"dom",
|
||||
"html",
|
||||
"parser",
|
||||
"querypath",
|
||||
"serializer",
|
||||
"xml"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/Masterminds/html5-php/issues",
|
||||
"source": "https://github.com/Masterminds/html5-php/tree/2.10.0"
|
||||
},
|
||||
"time": "2025-07-25T09:04:22+00:00"
|
||||
},
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
"version": "3.10.0",
|
||||
@ -2524,6 +2976,201 @@
|
||||
],
|
||||
"time": "2026-02-16T23:10:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phenx/php-font-lib",
|
||||
"version": "0.5.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dompdf/php-font-lib.git",
|
||||
"reference": "a1681e9793040740a405ac5b189275059e2a9863"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a1681e9793040740a405ac5b189275059e2a9863",
|
||||
"reference": "a1681e9793040740a405ac5b189275059e2a9863",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"FontLib\\": "src/FontLib"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-2.1-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Ménager",
|
||||
"email": "fabien.menager@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A library to read, parse, export and make subsets of different types of font files.",
|
||||
"homepage": "https://github.com/PhenX/php-font-lib",
|
||||
"support": {
|
||||
"issues": "https://github.com/dompdf/php-font-lib/issues",
|
||||
"source": "https://github.com/dompdf/php-font-lib/tree/0.5.6"
|
||||
},
|
||||
"time": "2024-01-29T14:45:26+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phenx/php-svg-lib",
|
||||
"version": "0.5.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dompdf/php-svg-lib.git",
|
||||
"reference": "46b25da81613a9cf43c83b2a8c2c1bdab27df691"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/46b25da81613a9cf43c83b2a8c2c1bdab27df691",
|
||||
"reference": "46b25da81613a9cf43c83b2a8c2c1bdab27df691",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"php": "^7.1 || ^8.0",
|
||||
"sabberworm/php-css-parser": "^8.4"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Svg\\": "src/Svg"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-3.0-or-later"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Ménager",
|
||||
"email": "fabien.menager@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "A library to read, parse and export to PDF SVG files.",
|
||||
"homepage": "https://github.com/PhenX/php-svg-lib",
|
||||
"support": {
|
||||
"issues": "https://github.com/dompdf/php-svg-lib/issues",
|
||||
"source": "https://github.com/dompdf/php-svg-lib/tree/0.5.4"
|
||||
},
|
||||
"time": "2024-04-08T12:52:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoffice/phpspreadsheet",
|
||||
"version": "1.29.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
|
||||
"reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/fde2ccf55eaef7e86021ff1acce26479160a0fa0",
|
||||
"reference": "fde2ccf55eaef7e86021ff1acce26479160a0fa0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-ctype": "*",
|
||||
"ext-dom": "*",
|
||||
"ext-fileinfo": "*",
|
||||
"ext-gd": "*",
|
||||
"ext-iconv": "*",
|
||||
"ext-libxml": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-simplexml": "*",
|
||||
"ext-xml": "*",
|
||||
"ext-xmlreader": "*",
|
||||
"ext-xmlwriter": "*",
|
||||
"ext-zip": "*",
|
||||
"ext-zlib": "*",
|
||||
"ezyang/htmlpurifier": "^4.15",
|
||||
"maennchen/zipstream-php": "^2.1 || ^3.0",
|
||||
"markbaker/complex": "^3.0",
|
||||
"markbaker/matrix": "^3.0",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"psr/http-client": "^1.0",
|
||||
"psr/http-factory": "^1.0",
|
||||
"psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
|
||||
"dompdf/dompdf": "^1.0 || ^2.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.2",
|
||||
"mitoteam/jpgraph": "^10.3",
|
||||
"mpdf/mpdf": "^8.1.1",
|
||||
"phpcompatibility/php-compatibility": "^9.3",
|
||||
"phpstan/phpstan": "^1.1",
|
||||
"phpstan/phpstan-phpunit": "^1.0",
|
||||
"phpunit/phpunit": "^8.5 || ^9.0 || ^10.0",
|
||||
"squizlabs/php_codesniffer": "^3.7",
|
||||
"tecnickcom/tcpdf": "^6.5"
|
||||
},
|
||||
"suggest": {
|
||||
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
|
||||
"ext-intl": "PHP Internationalization Functions",
|
||||
"mitoteam/jpgraph": "Option for rendering charts, or including charts with PDF or HTML Writers",
|
||||
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
|
||||
"tecnickcom/tcpdf": "Option for rendering PDF with PDF Writer"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"PhpOffice\\PhpSpreadsheet\\": "src/PhpSpreadsheet"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Maarten Balliauw",
|
||||
"homepage": "https://blog.maartenballiauw.be"
|
||||
},
|
||||
{
|
||||
"name": "Mark Baker",
|
||||
"homepage": "https://markbakeruk.net"
|
||||
},
|
||||
{
|
||||
"name": "Franck Lefevre",
|
||||
"homepage": "https://rootslabs.net"
|
||||
},
|
||||
{
|
||||
"name": "Erik Tilt"
|
||||
},
|
||||
{
|
||||
"name": "Adrien Crivelli"
|
||||
}
|
||||
],
|
||||
"description": "PHPSpreadsheet - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
|
||||
"homepage": "https://github.com/PHPOffice/PhpSpreadsheet",
|
||||
"keywords": [
|
||||
"OpenXML",
|
||||
"excel",
|
||||
"gnumeric",
|
||||
"ods",
|
||||
"php",
|
||||
"spreadsheet",
|
||||
"xls",
|
||||
"xlsx"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
|
||||
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.29.0"
|
||||
},
|
||||
"time": "2023-06-14T22:48:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpoption/phpoption",
|
||||
"version": "1.9.5",
|
||||
@ -3288,6 +3935,72 @@
|
||||
},
|
||||
"time": "2025-12-14T04:43:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sabberworm/php-css-parser",
|
||||
"version": "v8.9.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
|
||||
"reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d8e916507b88e389e26d4ab03c904a082aa66bb9",
|
||||
"reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-iconv": "*",
|
||||
"php": "^5.6.20 || ^7.0.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.41",
|
||||
"rawr/cross-data-providers": "^2.0.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "for parsing UTF-8 CSS"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "9.0.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Sabberworm\\CSS\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Raphael Schweikert"
|
||||
},
|
||||
{
|
||||
"name": "Oliver Klee",
|
||||
"email": "github@oliverklee.de"
|
||||
},
|
||||
{
|
||||
"name": "Jake Hotson",
|
||||
"email": "jake.github@qzdesign.co.uk"
|
||||
}
|
||||
],
|
||||
"description": "Parser for CSS Files written in PHP",
|
||||
"homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser",
|
||||
"keywords": [
|
||||
"css",
|
||||
"parser",
|
||||
"stylesheet"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues",
|
||||
"source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.9.0"
|
||||
},
|
||||
"time": "2025-07-11T13:20:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/clock",
|
||||
"version": "v7.4.8",
|
||||
|
||||
155
resources/views/auth/login.blade.php
Normal file
155
resources/views/auth/login.blade.php
Normal file
@ -0,0 +1,155 @@
|
||||
@extends('partials.main_auth')
|
||||
|
||||
@section('custom_css')
|
||||
<style>
|
||||
.container-fluid {
|
||||
padding: 0 10px !important;
|
||||
}
|
||||
|
||||
@media (max-width: 575.98px) {
|
||||
.hide-xs {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-8 hide-xs bg-secondary vh-100">
|
||||
<div class="d-flex align-items-center justify-content-center h-75 flex-column">
|
||||
<img src="{{ asset('metronic/assets/media/illustrations/sigma-1/10.png') }}" alt="" srcset="" class="h-75">
|
||||
<div class="mt-5">
|
||||
<span class="fs-1 fw-bold" id="title_selamat_datang"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4 py-10 px-5">
|
||||
<div class="d-flex align-items-center justify-content-center h-100 flex-column">
|
||||
<center>
|
||||
<img src="{{ asset('assets/img/logo-fullname.png') }}" alt="" srcset="" class="w-50">
|
||||
<h1 class="mt-5">Login Admin Pra Akreditasi</h1>
|
||||
</center>
|
||||
<form id="form_login" class="form mt-10 w-75" action="/login" autocomplete="off" method="POST">
|
||||
@csrf
|
||||
<div class="fv-row mb-7">
|
||||
<label class="required fw-semibold fs-6 mb-2">Username</label>
|
||||
<input type="text" name="namauser" class="form-control mb-3 mb-lg-0" placeholder="" value="" />
|
||||
</div>
|
||||
<div class="fv-row mb-7">
|
||||
<label class="required fw-semibold fs-6 mb-2">Password</label>
|
||||
<div class="input-group mb-3">
|
||||
<input type="password" name="password" id="password" class="form-control" placeholder="Password">
|
||||
<button class="btn btn-primary" type="button" id="togglePassword">
|
||||
<i class="fa fa-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button id="form_permintaan_submit" type="submit" class="btn btn-primary w-100 mt-10">
|
||||
<span class="indicator-label">
|
||||
Login
|
||||
</span>
|
||||
<span class="indicator-progress">
|
||||
Please wait... <span class="spinner-border spinner-border-sm align-middle ms-2"></span>
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('custom_js')
|
||||
<script>
|
||||
@if (session('error'))
|
||||
toastr.error("{{ session('error') }}");
|
||||
@endif
|
||||
|
||||
$(document).ready(function() {
|
||||
$('#togglePassword').on('click', function () {
|
||||
let input = $('#password');
|
||||
let icon = $(this).find('i');
|
||||
|
||||
if (input.attr('type') === 'password') {
|
||||
input.attr('type', 'text');
|
||||
icon.removeClass('fa-eye').addClass('fa-eye-slash');
|
||||
} else {
|
||||
input.attr('type', 'password');
|
||||
icon.removeClass('fa-eye-slash').addClass('fa-eye');
|
||||
}
|
||||
});
|
||||
});
|
||||
var typed = new Typed("#title_selamat_datang", {
|
||||
strings: ["Selamat Datang", "Di Halaman Admin", "Permintaan IT", "Silahkan Masukkan Username Dan Password", "Jika Belum Memiliki Akun", "Silahkan Hubungi Tim IT"],
|
||||
typeSpeed: 30,
|
||||
});
|
||||
|
||||
// Define form element
|
||||
const form = document.getElementById('form_login');
|
||||
const token = $('meta[name="csrf-token"]').attr('content');
|
||||
|
||||
// Init form validation rules. For more info check the FormValidation plugin's official documentation:https://formvalidation.io/
|
||||
var validator = FormValidation.formValidation(
|
||||
form,
|
||||
{
|
||||
fields: {
|
||||
'username': {
|
||||
validators: {
|
||||
notEmpty: {
|
||||
message: 'Username masih kosong'
|
||||
}
|
||||
}
|
||||
},
|
||||
'password': {
|
||||
validators: {
|
||||
notEmpty: {
|
||||
message: 'Password masih kosong'
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
plugins: {
|
||||
trigger: new FormValidation.plugins.Trigger(),
|
||||
bootstrap: new FormValidation.plugins.Bootstrap5({
|
||||
rowSelector: '.fv-row',
|
||||
eleInvalidClass: '',
|
||||
eleValidClass: ''
|
||||
})
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Submit button handler
|
||||
const submitButton = document.getElementById('form_permintaan_submit');
|
||||
submitButton.addEventListener('click', function (e) {
|
||||
// Prevent default button action
|
||||
e.preventDefault();
|
||||
|
||||
// Validate form before submit
|
||||
if (validator) {
|
||||
validator.validate().then(function (status) {
|
||||
if (status == 'Valid') {
|
||||
submitButton.setAttribute('data-kt-indicator', 'on');
|
||||
submitButton.disabled = true;
|
||||
setTimeout(function () {
|
||||
submitButton.removeAttribute('data-kt-indicator');
|
||||
submitButton.disabled = false;
|
||||
}, 2000);
|
||||
|
||||
if (!form.querySelector('input[name="_token"]')) {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = '_token';
|
||||
input.value = token;
|
||||
form.appendChild(input);
|
||||
}
|
||||
form.submit();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
222
resources/views/master_pitstop/index.blade.php
Normal file
222
resources/views/master_pitstop/index.blade.php
Normal file
@ -0,0 +1,222 @@
|
||||
@extends('partials.main')
|
||||
|
||||
@section('content')
|
||||
<div class="card card-flush">
|
||||
<div class="card-header pt-7">
|
||||
<div class="card-title">
|
||||
<h2 class="mb-0 fw-bold">Master PitStop</h2>
|
||||
</div>
|
||||
<div class="card-toolbar">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="d-flex align-items-center position-relative">
|
||||
<span class="svg-icon svg-icon-1 position-absolute ms-4">
|
||||
<i class="fa-solid fa-magnifying-glass"></i>
|
||||
</span>
|
||||
<input type="text" id="searchMaster" class="form-control form-control-solid w-300px ps-12" placeholder="Cari pitstop..." autocomplete="off" />
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-primary" id="btnAdd">
|
||||
<i class="fa-solid fa-plus me-2"></i>Tambah
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body pt-3">
|
||||
<div class="table-responsive">
|
||||
<table id="tblMaster" class="table align-middle table-row-dashed fs-6 gy-3 w-100">
|
||||
<thead>
|
||||
<tr class="text-muted fw-semibold fs-7 text-uppercase gs-0">
|
||||
<th style="width: 70px">ID</th>
|
||||
<th>Nama</th>
|
||||
<th style="width: 130px">Status</th>
|
||||
<th class="text-end" style="width: 220px">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="fw-semibold text-gray-800"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modalMaster" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<form id="formMaster">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="modalTitle">Tambah Master PitStop</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="master_id" />
|
||||
<div class="mb-4">
|
||||
<label for="nama" class="form-label">Nama</label>
|
||||
<input type="text" id="nama" class="form-control" placeholder="Nama pitstop..." required />
|
||||
<div class="form-text">Contoh: Upload Berkas, Verifikasi, dsb.</div>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="statusenabled" checked />
|
||||
<label class="form-check-label" for="statusenabled">Aktif</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-light" data-bs-dismiss="modal">Batal</button>
|
||||
<button type="submit" class="btn btn-primary" id="btnSave">
|
||||
<span class="indicator-label">Simpan</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('custom_js')
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
const csrf = $('meta[name="csrf-token"]').attr('content');
|
||||
|
||||
const escapeHtml = (str) => String(str ?? '').replace(/[&<>"']/g, (m) => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
}[m]));
|
||||
|
||||
const table = $('#tblMaster').DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
searchDelay: 350,
|
||||
dom: 'rtip',
|
||||
pageLength: 10,
|
||||
lengthMenu: [10, 20, 50, 100],
|
||||
order: [[0, 'asc']],
|
||||
ajax: { url: '/master-pitstop/data', type: 'GET' },
|
||||
columns: [
|
||||
{ data: 'id' },
|
||||
{ data: 'nama', render: (d) => `<span class="fw-bold">${escapeHtml(d)}</span>` },
|
||||
{
|
||||
data: 'statusenabled',
|
||||
render: (v) => (v ? '<span class="badge badge-light-success">Aktif</span>' : '<span class="badge badge-light-danger">Nonaktif</span>'),
|
||||
},
|
||||
{
|
||||
data: null,
|
||||
className: 'text-end',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
render: function (data, type, row) {
|
||||
const toggleLabel = row.statusenabled ? 'Nonaktifkan' : 'Aktifkan';
|
||||
const toggleClass = row.statusenabled ? 'btn-light-danger' : 'btn-light-success';
|
||||
return `
|
||||
<button type="button" class="btn btn-sm btn-light editRow"
|
||||
data-id="${row.id}"
|
||||
data-nama="${escapeHtml(row.nama)}"
|
||||
data-status="${row.statusenabled ? 1 : 0}"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm ${toggleClass} toggleRow" data-id="${row.id}">
|
||||
${toggleLabel}
|
||||
</button>`;
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
let timer = null;
|
||||
$('#searchMaster').on('keyup', function () {
|
||||
const val = String($(this).val() ?? '');
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(function () {
|
||||
table.search(val).draw();
|
||||
}, 250);
|
||||
});
|
||||
|
||||
const modalEl = document.getElementById('modalMaster');
|
||||
const modal = new bootstrap.Modal(modalEl);
|
||||
|
||||
const resetForm = () => {
|
||||
$('#master_id').val('');
|
||||
$('#nama').val('');
|
||||
$('#statusenabled').prop('checked', true);
|
||||
$('#modalTitle').text('Tambah Master PitStop');
|
||||
};
|
||||
|
||||
$('#btnAdd').on('click', function () {
|
||||
resetForm();
|
||||
modal.show();
|
||||
});
|
||||
|
||||
$(document).on('click', '.editRow', function () {
|
||||
resetForm();
|
||||
$('#master_id').val($(this).data('id'));
|
||||
$('#nama').val($(this).data('nama'));
|
||||
$('#statusenabled').prop('checked', Number($(this).data('status')) === 1);
|
||||
$('#modalTitle').text('Edit Master PitStop');
|
||||
modal.show();
|
||||
});
|
||||
|
||||
$('#formMaster').on('submit', function (e) {
|
||||
e.preventDefault();
|
||||
const id = String($('#master_id').val() ?? '').trim();
|
||||
const payload = {
|
||||
nama: $('#nama').val(),
|
||||
statusenabled: $('#statusenabled').is(':checked') ? 1 : 0,
|
||||
};
|
||||
|
||||
const url = id ? `/master-pitstop/${id}` : '/master-pitstop';
|
||||
const method = id ? 'PUT' : 'POST';
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
method,
|
||||
headers: { 'X-CSRF-TOKEN': csrf },
|
||||
data: payload,
|
||||
})
|
||||
.done(function () {
|
||||
modal.hide();
|
||||
table.ajax.reload(null, false);
|
||||
})
|
||||
.fail(function (xhr) {
|
||||
const msg =
|
||||
xhr?.responseJSON?.message ||
|
||||
(xhr?.status === 422 ? 'Validasi gagal.' : 'Gagal menyimpan data.');
|
||||
Swal.fire({
|
||||
toast: true,
|
||||
position: 'top-end',
|
||||
icon: 'error',
|
||||
title: msg,
|
||||
showConfirmButton: false,
|
||||
timer: 2600,
|
||||
timerProgressBar: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.toggleRow', function () {
|
||||
const id = $(this).data('id');
|
||||
|
||||
$.ajax({
|
||||
url: `/master-pitstop/${id}/toggle`,
|
||||
method: 'PATCH',
|
||||
headers: { 'X-CSRF-TOKEN': csrf },
|
||||
})
|
||||
.done(function () {
|
||||
table.ajax.reload(null, false);
|
||||
})
|
||||
.fail(function () {
|
||||
Swal.fire({
|
||||
toast: true,
|
||||
position: 'top-end',
|
||||
icon: 'error',
|
||||
title: 'Gagal mengubah status.',
|
||||
showConfirmButton: false,
|
||||
timer: 2600,
|
||||
timerProgressBar: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
82
resources/views/partials/main_auth.blade.php
Normal file
82
resources/views/partials/main_auth.blade.php
Normal file
@ -0,0 +1,82 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<!--begin::Head-->
|
||||
<head><base href=""/>
|
||||
<title>Pra Akreditasi RSAB Harapan Kita</title>
|
||||
<meta property="og:type" content="article" />
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="canonical" />
|
||||
<link rel="shortcut icon" href="{{ asset('assets/img/favicon.png') }}" />
|
||||
<!--begin::Fonts(mandatory for all pages)-->
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Inter:300,400,500,600,700" />
|
||||
<!--end::Fonts-->
|
||||
<!--begin::Vendor Stylesheets(used for this page only)-->
|
||||
<link href="{{ asset('metronic/assets/plugins/custom/fullcalendar/fullcalendar.bundle.css') }}" rel="stylesheet" type="text/css" />
|
||||
<link href="{{ asset('metronic/assets/plugins/custom/datatables/datatables.bundle.css') }}" rel="stylesheet" type="text/css" />
|
||||
<!--end::Vendor Stylesheets-->
|
||||
<!--begin::Global Stylesheets Bundle(mandatory for all pages)-->
|
||||
<link href="{{ asset('metronic/assets/plugins/global/plugins.bundle.css') }}" rel="stylesheet" type="text/css" />
|
||||
<link href="{{ asset('metronic/assets/css/style.bundle.css') }}" rel="stylesheet" type="text/css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" integrity="sha512-2SwdPD6INVrV/lHTZbO2nodKhrnDdJK9/kg2XD1r9uGqPo1cUbujc+IYdlYdEErWNu69gVcYgdxlmVmzTWnetw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" integrity="sha512-2SwdPD6INVrV/lHTZbO2nodKhrnDdJK9/kg2XD1r9uGqPo1cUbujc+IYdlYdEErWNu69gVcYgdxlmVmzTWnetw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
<!--end::Global Stylesheets Bundle-->
|
||||
<script>// Frame-busting to prevent site from being loaded within a frame without permission (click-jacking) if (window.top != window.self) { window.top.location.replace(window.self.location.href); }</script>
|
||||
@yield('custom_css')
|
||||
</head>
|
||||
<!--end::Head-->
|
||||
<!--begin::Body-->
|
||||
<body id="kt_body">
|
||||
<!--begin::Theme mode setup on page load-->
|
||||
<script>var defaultThemeMode = "light"; var themeMode; if ( document.documentElement ) { if ( document.documentElement.hasAttribute("data-bs-theme-mode")) { themeMode = document.documentElement.getAttribute("data-bs-theme-mode"); } else { if ( localStorage.getItem("data-bs-theme") !== null ) { themeMode = localStorage.getItem("data-bs-theme"); } else { themeMode = defaultThemeMode; } } if (themeMode === "system") { themeMode = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; } document.documentElement.setAttribute("data-bs-theme", themeMode); }</script>
|
||||
<!--end::Theme mode setup on page load-->
|
||||
<!--begin::Main-->
|
||||
<!--begin::Root-->
|
||||
<div class="d-flex flex-column flex-root">
|
||||
@yield('content')
|
||||
<!--begin::Page-->
|
||||
<!--end::Page-->
|
||||
</div>
|
||||
<!--begin::Javascript-->
|
||||
<script>var hostUrl = "assets/";</script>
|
||||
<!--begin::Global Javascript Bundle(mandatory for all pages)-->
|
||||
<script src="{{ asset('metronic/assets/plugins/global/plugins.bundle.js') }}"></script>
|
||||
<script src="{{ asset('metronic/assets/js/scripts.bundle.js') }}"></script>
|
||||
<!--end::Global Javascript Bundle-->
|
||||
<!--begin::Vendors Javascript(used for this page only)-->
|
||||
<script src="{{ asset('metronic/assets/plugins/custom/fullcalendar/fullcalendar.bundle.js') }}"></script>
|
||||
<script src="{{ asset('metronic/assets/plugins/custom/datatables/datatables.bundle.js') }}"></script>
|
||||
<!--end::Vendors Javascript-->
|
||||
<!--begin::Custom Javascript(used for this page only)-->
|
||||
<script src="{{ asset('metronic/assets/js/widgets.bundle.js') }}"></script>
|
||||
<script src="{{ asset('metronic/assets/js/custom/widgets.js') }}"></script>
|
||||
<script src="{{ asset('metronic/assets/js/custom/apps/chat/chat.js') }}"></script>
|
||||
<script src="{{ asset('metronic/assets/js/custom/utilities/modals/users-search.js') }}"></script>
|
||||
<script src="{{ asset('metronic/assets/plugins/custom/typedjs/typedjs.bundle.js') }}"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/js/all.min.js" integrity="sha512-6BTOlkauINO65nLhXhthZMtepgJSghyimIalb+crKRPhvhmsCdnIuGcVbR5/aQY2A+260iC1OPy1oCdB6pSSwQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script>
|
||||
toastr.options = {
|
||||
"closeButton": false,
|
||||
"debug": false,
|
||||
"newestOnTop": false,
|
||||
"progressBar": false,
|
||||
"positionClass": "toastr-top-right",
|
||||
"preventDuplicates": false,
|
||||
"onclick": null,
|
||||
"showDuration": "300",
|
||||
"hideDuration": "1000",
|
||||
"timeOut": "5000",
|
||||
"extendedTimeOut": "1000",
|
||||
"showEasing": "swing",
|
||||
"hideEasing": "linear",
|
||||
"showMethod": "fadeIn",
|
||||
"hideMethod": "fadeOut"
|
||||
};
|
||||
</script>
|
||||
|
||||
<!--end::Custom Javascript-->
|
||||
<!--end::Javascript-->
|
||||
@yield('custom_js')
|
||||
</body>
|
||||
<!--end::Body-->
|
||||
</html>
|
||||
@ -13,6 +13,17 @@
|
||||
<div class="w-100 hover-scroll-overlay-y pe-2 me-2" id="kt_aside_menu_wrapper" data-kt-scroll="true" data-kt-scroll-activate="{default: false, lg: true}" data-kt-scroll-height="auto" data-kt-scroll-dependencies="#kt_aside_logo, #kt_aside_user, #kt_aside_footer" data-kt-scroll-wrappers="#kt_aside, #kt_aside_menu, #kt_aside_menu_wrapper" data-kt-scroll-offset="0">
|
||||
<!--begin::Menu-->
|
||||
<div class="menu menu-column menu-rounded menu-active-bg fw-semibold" id="#kt_aside_menu" data-kt-menu="true">
|
||||
<div class="menu-item">
|
||||
<a class="menu-link" href="/">
|
||||
<span class="menu-icon">
|
||||
<i class="fa-solid fa-layer-group fs-2">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
</i>
|
||||
</span>
|
||||
<span class="menu-title">Monitoring Pra Akreditasi</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<a class="menu-link" href="/pitstop">
|
||||
<span class="menu-icon">
|
||||
@ -26,16 +37,18 @@
|
||||
</div>
|
||||
|
||||
<div class="menu-item">
|
||||
<a class="menu-link" href="/pitstop/progress">
|
||||
<a class="menu-link" href="/master-pitstop">
|
||||
<span class="menu-icon">
|
||||
<i class="fa-solid fa-chart-simple fs-2">
|
||||
<i class="fa-solid fa-list-check fs-2">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
</i>
|
||||
</span>
|
||||
<span class="menu-title">Dashboard Progress</span>
|
||||
<span class="menu-title">Master PitStop</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<!--end::Menu-->
|
||||
</div>
|
||||
@ -46,16 +59,17 @@
|
||||
<!--begin::User panel-->
|
||||
<div class="d-flex flex-stack ms-7">
|
||||
<!--begin::Link-->
|
||||
<a href="/logout" class="btn btn-sm btn-icon btn-active-color-primary btn-icon-gray-600 btn-text-gray-600">
|
||||
<form action="/logout" method="POST">
|
||||
@csrf
|
||||
|
||||
<button type="submit" class="btn btn-sm btn-icon btn-active-color-primary btn-icon-gray-600 btn-text-gray-600">
|
||||
<i class="fa-solid fa-right-from-bracket fs-1 me-2">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
</i>
|
||||
<!--begin::Major-->
|
||||
<span class="d-flex flex-shrink-0 fw-bold">Log Out</span>
|
||||
<!--end::Major-->
|
||||
</a>
|
||||
<!--end::Link-->
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<!--end::User panel-->
|
||||
</div>
|
||||
|
||||
@ -7,6 +7,16 @@
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.pitstop-type-sticky {
|
||||
position: sticky;
|
||||
top: 16px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#resultList .list-group-item-action:hover {
|
||||
background-color: var(--bs-gray-100);
|
||||
}
|
||||
|
||||
/* Catatan: styling <option> tergantung browser, tapi ini membantu di banyak kasus */
|
||||
#step option[data-locked='1'] {
|
||||
color: var(--bs-success) !important;
|
||||
@ -27,7 +37,25 @@
|
||||
</div>
|
||||
|
||||
<div class="card-body pt-0">
|
||||
<div class="w-100">
|
||||
<div class="row g-4 align-items-end">
|
||||
<div class="col-12 col-md-4 align-self-start">
|
||||
<div class="pitstop-type-sticky">
|
||||
<label class="form-label fw-semibold mb-2">Tipe Karyawan</label>
|
||||
<div class="d-flex flex-wrap gap-6">
|
||||
<label class="form-check form-check-sm form-check-custom form-check-solid mb-0">
|
||||
<input class="form-check-input" type="radio" name="karyawan_type" id="karyawanTypeInternal" value="internal" checked />
|
||||
<span class="form-check-label fw-semibold">Internal</span>
|
||||
</label>
|
||||
<label class="form-check form-check-sm form-check-custom form-check-solid mb-0">
|
||||
<input class="form-check-input" type="radio" name="karyawan_type" id="karyawanTypeLuar" value="luar" />
|
||||
<span class="form-check-label fw-semibold">Karyawan Luar</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-text">Mode pencarian akan menyesuaikan tipe karyawan.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-8">
|
||||
<label for="searchKaryawan" class="form-label fw-semibold">Cari Karyawan</label>
|
||||
<input
|
||||
type="text"
|
||||
@ -41,6 +69,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modalPitstop" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
@ -59,6 +88,11 @@
|
||||
<input type="text" id="nama_karyawan" class="form-control" readonly />
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="nip" class="form-label" id="label_nip">NIP</label>
|
||||
<input type="text" id="nip" class="form-control" readonly />
|
||||
</div>
|
||||
|
||||
<div class="notice d-flex bg-light-warning rounded border-warning border border-dashed p-4 mb-5">
|
||||
<div class="d-flex flex-stack flex-grow-1">
|
||||
<div class="fw-semibold">
|
||||
@ -72,7 +106,7 @@
|
||||
<label for="step" class="form-label">Step</label>
|
||||
<select id="step" name="step" class="form-select" required>
|
||||
@forelse ($masterPitStop as $ps)
|
||||
<option value="{{ $ps->id }}">{{ $ps->nama }}</option>
|
||||
<option value="{{ $ps->id }}">({{ $ps->id }}) {{ $ps->nama }}</option>
|
||||
@empty
|
||||
<option value="" disabled selected>Tidak ada data</option>
|
||||
@endforelse
|
||||
@ -121,6 +155,14 @@
|
||||
|
||||
const csrf = $('meta[name="csrf-token"]').attr('content');
|
||||
|
||||
const escapeHtml = (str) => String(str ?? '').replace(/[&<>"']/g, (m) => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
}[m]));
|
||||
|
||||
const renderMessage = (message) => {
|
||||
$resultList.html(`<li class="list-group-item text-muted">${message}</li>`);
|
||||
};
|
||||
@ -129,12 +171,26 @@
|
||||
$resultList.empty();
|
||||
};
|
||||
|
||||
const getKaryawanType = () => $('input[name="karyawan_type"]:checked').val() || 'internal';
|
||||
|
||||
const getListEndpoint = () => (getKaryawanType() === 'luar' ? '/list-karyawan-luar' : '/list-karyawan');
|
||||
|
||||
const syncPlaceholder = () => {
|
||||
const type = getKaryawanType();
|
||||
$search.attr('placeholder', type === 'luar' ? 'Ketik nama / NIK...' : 'Ketik nama / NIP...');
|
||||
};
|
||||
|
||||
const searchKaryawan = (keyword) => {
|
||||
if (activeRequest) activeRequest.abort();
|
||||
|
||||
renderMessage('Mencari...');
|
||||
|
||||
activeRequest = $.get('/list-karyawan', { search: keyword, per_page: 20 })
|
||||
const endpoint = getListEndpoint();
|
||||
const params = getKaryawanType() === 'luar'
|
||||
? { search: keyword, limit: 50 }
|
||||
: { search: keyword, limit: 50 };
|
||||
|
||||
activeRequest = $.get(endpoint, params)
|
||||
.done(function (res) {
|
||||
const data = res?.data ?? [];
|
||||
if (!data.length) {
|
||||
@ -145,13 +201,23 @@
|
||||
const html = data
|
||||
.map((item) => {
|
||||
const nama = item?.namalengkap ?? '-';
|
||||
const nip = item?.nip_pns ?? '-';
|
||||
const unit = item?.unit_name ?? '-';
|
||||
return `
|
||||
<li class="list-group-item list-group-item-action pilihKaryawan"
|
||||
role="button"
|
||||
data-id="${item.id}"
|
||||
data-nama="${nama}"
|
||||
data-nama="${escapeHtml(nama)}"
|
||||
data-nip="${escapeHtml(nip)}"
|
||||
data-unit="${escapeHtml(unit)}"
|
||||
>
|
||||
${nama}
|
||||
<div class="d-flex align-items-center justify-content-between gap-3">
|
||||
<div class="d-flex flex-column">
|
||||
<span class="fw-semibold">${escapeHtml(nama)}</span>
|
||||
<span class="text-muted fs-7">${escapeHtml(nip)}</span>
|
||||
</div>
|
||||
<span class="badge badge-light-primary">${escapeHtml(unit)}</span>
|
||||
</div>
|
||||
</li>`;
|
||||
})
|
||||
.join('');
|
||||
@ -166,42 +232,38 @@
|
||||
|
||||
const lockExistingSteps = (pegawaiId) => {
|
||||
$stepHint.html('');
|
||||
$step.find('option').each(function () {
|
||||
const $opt = $(this);
|
||||
const original = $opt.attr('data-original');
|
||||
if (original) $opt.text(original);
|
||||
$opt.prop('disabled', false).removeAttr('data-locked').removeAttr('data-original');
|
||||
});
|
||||
|
||||
// simpan semua opsi awal, supaya bisa di-restore setiap ganti pegawai
|
||||
if (!$step.data('allOptionsHtml')) {
|
||||
$step.data('allOptionsHtml', $step.html());
|
||||
} else {
|
||||
$step.html($step.data('allOptionsHtml'));
|
||||
}
|
||||
|
||||
$form.find('button[type="submit"]').prop('disabled', false);
|
||||
|
||||
return $.get('/pitstop/pegawai-steps', { pegawai_id: pegawaiId })
|
||||
const stepsEndpoint = getKaryawanType() === 'luar' ? '/pitstop/pegawai-steps-external' : '/pitstop/pegawai-steps';
|
||||
|
||||
return $.get(stepsEndpoint, { pegawai_id: pegawaiId })
|
||||
.done(function (res) {
|
||||
const lockedSteps = res?.data?.locked_steps ?? [];
|
||||
const locked = new Set(lockedSteps.map((v) => String(v)));
|
||||
|
||||
// hanya tampilkan step yang belum lulus (yang locked dihapus dari list)
|
||||
$step.find('option').each(function () {
|
||||
const val = String($(this).val());
|
||||
if (locked.has(val)) {
|
||||
const $opt = $(this);
|
||||
$opt.attr('data-original', $opt.text());
|
||||
$opt.text(`${$opt.text()} (Selesai)`);
|
||||
$opt.prop('disabled', true).attr('data-locked', '1');
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
|
||||
if ($step.find('option:selected').prop('disabled')) {
|
||||
const $firstEnabled = $step.find('option:not([disabled])').first();
|
||||
if ($firstEnabled.length) $step.val($firstEnabled.val());
|
||||
}
|
||||
const $first = $step.find('option').first();
|
||||
if ($first.length) $step.val($first.val());
|
||||
|
||||
const enabledCount = $step.find('option:not([disabled])').length;
|
||||
if (!enabledCount) {
|
||||
const availableCount = $step.find('option').length;
|
||||
if (!availableCount) {
|
||||
$form.find('button[type="submit"]').prop('disabled', true);
|
||||
}
|
||||
|
||||
const selectedLocked = $step.find('option:selected').attr('data-locked') === '1';
|
||||
if (selectedLocked) {
|
||||
$stepHint.html('<span class="text-success fw-semibold">Selesai</span>');
|
||||
$stepHint.html('<span class="text-success fw-semibold">Semua step sudah lulus</span>');
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
@ -210,8 +272,8 @@
|
||||
};
|
||||
|
||||
$step.on('change', function () {
|
||||
const selectedLocked = $step.find('option:selected').attr('data-locked') === '1';
|
||||
$stepHint.html(selectedLocked ? '<span class="text-success fw-semibold">Selesai</span>' : '');
|
||||
// opsi yang tampil sudah pasti belum lulus
|
||||
$stepHint.html('');
|
||||
});
|
||||
|
||||
$search.on('keyup', function () {
|
||||
@ -227,11 +289,22 @@
|
||||
searchTimer = setTimeout(() => searchKaryawan(keyword), 250);
|
||||
});
|
||||
|
||||
$(document).on('change', 'input[name="karyawan_type"]', function () {
|
||||
if (activeRequest) activeRequest.abort();
|
||||
$search.val('');
|
||||
syncPlaceholder();
|
||||
clearResults();
|
||||
});
|
||||
|
||||
syncPlaceholder();
|
||||
|
||||
$(document).on('click', '.pilihKaryawan', function () {
|
||||
$form.trigger('reset');
|
||||
$('#nama_karyawan').val($(this).data('nama'));
|
||||
$('#nip').val($(this).data('nip'));
|
||||
$('#karyawan_id').val($(this).data('id'));
|
||||
lockExistingSteps($(this).data('id'));
|
||||
getKaryawanType() === 'luar' ? $("#label_nip").text('NIK') : $("#label_nip").text('NIP')
|
||||
new bootstrap.Modal(document.getElementById('modalPitstop')).show();
|
||||
});
|
||||
|
||||
@ -255,8 +328,8 @@
|
||||
karyawan_id: $('#karyawan_id').val(),
|
||||
step: $('#step').val(),
|
||||
status: $('input[name="status"]:checked').val(),
|
||||
tipe_karyawan: getKaryawanType()
|
||||
};
|
||||
|
||||
$.ajax({
|
||||
url: '/pitstop/submit',
|
||||
method: 'POST',
|
||||
|
||||
109
resources/views/pitstop/monitoring_pdf.blade.php
Normal file
109
resources/views/pitstop/monitoring_pdf.blade.php
Normal file
@ -0,0 +1,109 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{{ $title ?? 'Monitoring Pra Akreditasi' }}</title>
|
||||
<style>
|
||||
/* * { font-family: DejaVu Sans, sans-serif; } */
|
||||
body { font-size: 10px; color: #111827; }
|
||||
.h1 { font-size: 16px; font-weight: 700; margin: 0; }
|
||||
.meta { margin-top: 4px; color: #6b7280; font-size: 10px; }
|
||||
/* Jangan paksa 1 unit utuh di 1 halaman (bisa bikin halaman 1 kosong) */
|
||||
.unit { margin-top: 14px; }
|
||||
.unit-head { padding: 8px 10px; border: 1px solid #e5e7eb; background: #f9fafb; }
|
||||
.unit-title { font-size: 12px; font-weight: 700; margin: 0; }
|
||||
.unit-meta { margin-top: 3px; color: #6b7280; font-size: 10px; }
|
||||
.table { width: 100%; border-collapse: collapse; margin-top: 8px; }
|
||||
.table th, .table td { border: 1px solid #e5e7eb; padding: 6px; vertical-align: top; }
|
||||
.table th { background: #f9fafb; text-align: left; font-weight: 700; }
|
||||
.text-right { text-align: right; }
|
||||
.badge { display: inline-block; padding: 2px 6px; border-radius: 10px; font-size: 10px; background: #eef2ff; color: #3730a3; }
|
||||
.badge-ok { background: #dcfce7; color: #166534; }
|
||||
.badge-warn { background: #fff7ed; color: #9a3412; }
|
||||
.muted { color: #6b7280; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<div class="h1">{{ $title ?? 'Monitoring Pra Akreditasi' }}</div>
|
||||
<div class="meta">
|
||||
Dicetak: {{ optional($generatedAt)->format('Y-m-d H:i') }}
|
||||
</div>
|
||||
<div class="meta">
|
||||
Total PitStop Aktif: {{ (int) ($totalSteps ?? 0) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (($isSummaryOnly ?? false) === true)
|
||||
<table class="table" style="margin-top: 14px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 28px">No</th>
|
||||
<th>Unit</th>
|
||||
<th class="text-right" style="width: 110px">Karyawan Selesai / Total Karyawan</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse ($units ?? [] as $i => $u)
|
||||
<tr>
|
||||
<td class="text-right">{{ $i + 1 }}</td>
|
||||
<td>
|
||||
<div style="font-weight: 700;">{{ $u->unit_name }}</div>
|
||||
</td>
|
||||
<td class="text-right">{{ number_format((int) $u->pegawai_selesai) }}/{{ number_format((int) $u->total_pegawai) }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="5" class="muted">Tidak ada data.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
@forelse ($units ?? [] as $u)
|
||||
<div class="unit">
|
||||
<div class="unit-head">
|
||||
<div class="unit-title">{{ $u->unit_name }}</div>
|
||||
<div class="unit-meta">
|
||||
Total Karyawan: <b>{{ number_format((int) $u->total_pegawai) }}</b>
|
||||
| Karyawan Selesai: <b>{{ number_format((int) $u->pegawai_selesai) }}</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 28px">No</th>
|
||||
<th style="width: 210px">Nama</th>
|
||||
<th style="width: 120px">NIP</th>
|
||||
<th class="text-right" style="width: 120px">Lulus / Total PitStop</th>
|
||||
<th>PitStop Belum Selesai</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse ($u->pegawai ?? [] as $i => $p)
|
||||
@php
|
||||
$selesai = ((int) ($p->total_steps ?? 0)) > 0 && ((int) ($p->lulus_steps ?? 0)) >= ((int) ($p->total_steps ?? 0));
|
||||
@endphp
|
||||
<tr>
|
||||
<td class="text-right">{{ $i + 1 }}</td>
|
||||
<td>{{ $p->nama }}</td>
|
||||
<td>{{ $p->nip_pns }}</td>
|
||||
<td class="text-right">{{ number_format((int) $p->lulus_steps) }}/{{ number_format((int) $p->total_steps) }}</td>
|
||||
<td>{{ $selesai ? '-' : ($p->belum_selesai_steps !== '' ? $p->belum_selesai_steps : '-') }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="7" class="muted">Tidak ada data pegawai.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@empty
|
||||
<div class="muted" style="margin-top: 14px;">Tidak ada data.</div>
|
||||
@endforelse
|
||||
@endif
|
||||
</body>
|
||||
</html>
|
||||
106
resources/views/pitstop/monitoring_pdf_external.blade.php
Normal file
106
resources/views/pitstop/monitoring_pdf_external.blade.php
Normal file
@ -0,0 +1,106 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{{ $title ?? 'Monitoring Karyawan Luar' }}</title>
|
||||
<style>
|
||||
* { font-family: DejaVu Sans, sans-serif; }
|
||||
body { font-size: 10px; color: #111827; }
|
||||
.h1 { font-size: 16px; font-weight: 700; margin: 0; }
|
||||
.meta { margin-top: 4px; color: #6b7280; font-size: 10px; }
|
||||
.section { margin-top: 14px; }
|
||||
.section-head { padding: 8px 10px; border: 1px solid #e5e7eb; background: #f9fafb; }
|
||||
.section-title { font-size: 12px; font-weight: 700; margin: 0; }
|
||||
.section-meta { margin-top: 3px; color: #6b7280; font-size: 10px; }
|
||||
.table { width: 100%; border-collapse: collapse; margin-top: 8px; }
|
||||
.table th, .table td { border: 1px solid #e5e7eb; padding: 6px; vertical-align: top; }
|
||||
.table th { background: #f9fafb; text-align: left; font-weight: 700; }
|
||||
.text-right { text-align: right; }
|
||||
.muted { color: #6b7280; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<div class="h1">{{ $title ?? 'Monitoring Karyawan Luar' }}</div>
|
||||
<div class="meta">
|
||||
Dicetak: {{ optional($generatedAt)->format('Y-m-d H:i') }}
|
||||
</div>
|
||||
<div class="meta">
|
||||
Total PitStop Aktif: {{ (int) ($totalSteps ?? 0) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (($isSummaryOnly ?? false) === true)
|
||||
<table class="table" style="margin-top: 14px;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 28px">No</th>
|
||||
<th>Tipe</th>
|
||||
<th class="text-right" style="width: 130px">Selesai / Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse ($types ?? [] as $i => $t)
|
||||
<tr>
|
||||
<td class="text-right">{{ $i + 1 }}</td>
|
||||
<td>
|
||||
<div style="font-weight: 700;">{{ $t->tipe }}</div>
|
||||
</td>
|
||||
<td class="text-right">{{ number_format((int) $t->pegawai_selesai) }}/{{ number_format((int) $t->total_pegawai) }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="3" class="muted">Tidak ada data.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
@forelse ($types ?? [] as $t)
|
||||
<div class="section">
|
||||
<div class="section-head">
|
||||
<div class="section-title">{{ $t->tipe }}</div>
|
||||
<div class="section-meta">
|
||||
Total Karyawan: <b>{{ number_format((int) $t->total_pegawai) }}</b>
|
||||
| Karyawan Selesai: <b>{{ number_format((int) $t->pegawai_selesai) }}</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 28px">No</th>
|
||||
<th style="width: 230px">Nama</th>
|
||||
<th style="width: 140px">NIK</th>
|
||||
<th class="text-right" style="width: 120px">Lulus / Total PitStop</th>
|
||||
<th>PitStop Belum Selesai</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse ($t->pegawai ?? [] as $i => $p)
|
||||
@php
|
||||
$selesai = ((int) ($p->total_steps ?? 0)) > 0 && ((int) ($p->lulus_steps ?? 0)) >= ((int) ($p->total_steps ?? 0));
|
||||
@endphp
|
||||
<tr>
|
||||
<td class="text-right">{{ $i + 1 }}</td>
|
||||
<td>{{ $p->nama }}</td>
|
||||
<td>{{ $p->nik }}</td>
|
||||
<td class="text-right">{{ number_format((int) $p->lulus_steps) }}/{{ number_format((int) $p->total_steps) }}</td>
|
||||
<td>{{ $selesai ? '-' : ($p->belum_selesai_steps !== '' ? $p->belum_selesai_steps : '-') }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="5" class="muted">Tidak ada data karyawan.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@empty
|
||||
<div class="muted" style="margin-top: 14px;">Tidak ada data.</div>
|
||||
@endforelse
|
||||
@endif
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -1,51 +0,0 @@
|
||||
<div class="d-flex flex-wrap justify-content-between align-items-center mt-5 gap-3">
|
||||
<div class="text-muted fs-7">
|
||||
Halaman {{ $users->currentPage() }} dari {{ $users->lastPage() }} ({{ $users->total() }} data)
|
||||
</div>
|
||||
|
||||
@php
|
||||
$current = $users->currentPage();
|
||||
$last = $users->lastPage();
|
||||
$start = max(1, $current - 2);
|
||||
$end = min($last, $current + 2);
|
||||
@endphp
|
||||
|
||||
<div class="d-flex align-items-center gap-2" id="progressPager">
|
||||
@if ($users->onFirstPage())
|
||||
<button type="button" class="btn btn-sm btn-light" disabled>Sebelumnya</button>
|
||||
@else
|
||||
<a class="btn btn-sm btn-light" href="{{ $users->previousPageUrl() }}">Sebelumnya</a>
|
||||
@endif
|
||||
|
||||
<div class="d-flex align-items-center gap-1">
|
||||
@if ($start > 1)
|
||||
<a class="btn btn-sm btn-light" href="{{ $users->url(1) }}">1</a>
|
||||
@if ($start > 2)
|
||||
<span class="px-2 text-muted">...</span>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
@for ($i = $start; $i <= $end; $i++)
|
||||
@if ($i === $current)
|
||||
<button type="button" class="btn btn-sm btn-primary" disabled>{{ $i }}</button>
|
||||
@else
|
||||
<a class="btn btn-sm btn-light" href="{{ $users->url($i) }}">{{ $i }}</a>
|
||||
@endif
|
||||
@endfor
|
||||
|
||||
@if ($end < $last)
|
||||
@if ($end < $last - 1)
|
||||
<span class="px-2 text-muted">...</span>
|
||||
@endif
|
||||
<a class="btn btn-sm btn-light" href="{{ $users->url($last) }}">{{ $last }}</a>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if ($users->hasMorePages())
|
||||
<a class="btn btn-sm btn-light" href="{{ $users->nextPageUrl() }}">Berikutnya</a>
|
||||
@else
|
||||
<button type="button" class="btn btn-sm btn-light" disabled>Berikutnya</button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
@forelse ($users as $row)
|
||||
@php
|
||||
$done = (int) $row->lulus_count;
|
||||
$pct = $totalSteps > 0 ? round(($done / $totalSteps) * 100) : 0;
|
||||
@endphp
|
||||
<tr>
|
||||
<td>
|
||||
<a href="#" class="text-gray-800 text-hover-primary viewDetail" data-id="{{ $row->id }}" data-nama="{{ e($row->nama) }}">
|
||||
{{ $row->nama }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<div class="progress bg-light" style="height: 18px;">
|
||||
<div
|
||||
class="progress-bar bg-success d-flex align-items-center justify-content-center fw-semibold"
|
||||
role="progressbar"
|
||||
style="width: {{ $pct }}%; font-size: 12px;"
|
||||
aria-valuenow="{{ $pct }}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
>
|
||||
{{ $done }} / {{ $totalSteps }}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="2" class="text-center text-muted py-6">Belum ada data</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
@ -1,234 +0,0 @@
|
||||
@extends('partials.main')
|
||||
|
||||
@section('content')
|
||||
<div class="card card-flush mb-7">
|
||||
<div class="card-header pt-7">
|
||||
<div class="card-title">
|
||||
<h2 class="mb-0 fw-bold">Dashboard Progress</h2>
|
||||
</div>
|
||||
<div class="card-toolbar">
|
||||
<span class="text-muted">Progress per karyawan berdasarkan master pitstop aktif</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body pt-0">
|
||||
<div class="row g-6">
|
||||
<div class="col-md-4">
|
||||
<div class="card bg-light-primary border-0 h-100">
|
||||
<div class="card-body">
|
||||
<div class="fw-semibold text-muted">Total Step Aktif</div>
|
||||
<div class="fs-2hx fw-bold mt-2">{{ $totalSteps }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card bg-light-info border-0 h-100">
|
||||
<div class="card-body">
|
||||
<div class="fw-semibold text-muted">Total Karyawan</div>
|
||||
<div class="fs-2hx fw-bold mt-2" id="totalUsersVal">{{ $totalUsers }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="card bg-light-success border-0 h-100">
|
||||
<div class="card-body">
|
||||
<div class="fw-semibold text-muted">Total Step Lulus</div>
|
||||
<div class="fs-2hx fw-bold mt-2 text-success">
|
||||
{{ $totalSelesai }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-flush">
|
||||
<div class="card-header pt-7">
|
||||
<div class="card-title">
|
||||
<h3 class="mb-0 fw-bold">Progress per Karyawan</h3>
|
||||
</div>
|
||||
<div class="card-toolbar">
|
||||
<form method="get" class="d-flex align-items-center gap-2" id="filterForm">
|
||||
<div class="d-flex align-items-center position-relative my-1">
|
||||
<span class="svg-icon svg-icon-1 position-absolute ms-4">
|
||||
<i class="fa-solid fa-magnifying-glass"></i>
|
||||
</span>
|
||||
<input type="text" name="q" value="{{ $q ?? '' }}" class="form-control form-control-solid w-250px ps-12" placeholder="Cari nama..." />
|
||||
</div>
|
||||
<select name="per_page" class="form-select form-select-solid w-125px">
|
||||
@foreach ([10, 20, 50, 100] as $n)
|
||||
<option value="{{ $n }}" @selected(((int) ($perPage ?? 20)) === $n)>{{ $n }}/page</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<button type="submit" class="btn btn-sm btn-primary">Cari</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body pt-3">
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle table-row-dashed fs-6 gy-3">
|
||||
<thead>
|
||||
<tr class="text-muted fw-semibold fs-7 text-uppercase gs-0">
|
||||
<th>Nama</th>
|
||||
<th style="width: 320px">Progress</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="fw-semibold text-gray-800" id="tableUsers">
|
||||
@include('pitstop.partials.progress_rows', ['users' => $users, 'totalSteps' => $totalSteps])
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="pagerWrap">
|
||||
@include('pitstop.partials.progress_pager', ['users' => $users])
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
<div class="modal fade" id="modalDetail" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Detail Progress</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="text-muted mb-4" id="detailNama"></div>
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle table-row-dashed fs-6 gy-3">
|
||||
<thead>
|
||||
<tr class="text-muted fw-semibold fs-7 text-uppercase gs-0">
|
||||
<th>Step (ID)</th>
|
||||
<th>Nama Step</th>
|
||||
<th>Status</th>
|
||||
<th class="text-end">Waktu</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="fw-semibold text-gray-800" id="detailTable">
|
||||
<tr>
|
||||
<td colspan="4" class="text-center text-muted py-6">Memuat data...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section('custom_js')
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
const $filterForm = $('#filterForm');
|
||||
const $pagerWrap = $('#pagerWrap');
|
||||
const $tableUsers = $('#tableUsers');
|
||||
const $totalUsersVal = $('#totalUsersVal');
|
||||
|
||||
const escapeHtml = (str) => String(str ?? '').replace(/[&<>"']/g, (m) => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
}[m]));
|
||||
|
||||
const withAjaxParam = (href) => {
|
||||
const url = new URL(href, window.location.origin);
|
||||
url.searchParams.set('ajax', '1');
|
||||
return url.toString();
|
||||
};
|
||||
|
||||
const setLoading = () => {
|
||||
$tableUsers.html('<tr><td colspan="2" class="text-center text-muted py-6">Memuat data...</td></tr>');
|
||||
};
|
||||
|
||||
const loadPage = (href, push = true) => {
|
||||
setLoading();
|
||||
|
||||
$.get(withAjaxParam(href))
|
||||
.done(function (res) {
|
||||
const data = res?.data ?? {};
|
||||
$totalUsersVal.text(data.totalUsers ?? 0);
|
||||
$tableUsers.html(data.table_html ?? '');
|
||||
$pagerWrap.html(data.pager_html ?? '');
|
||||
|
||||
if (push) {
|
||||
const urlNoAjax = new URL(href, window.location.origin);
|
||||
urlNoAjax.searchParams.delete('ajax');
|
||||
window.history.pushState({}, '', urlNoAjax.toString());
|
||||
}
|
||||
})
|
||||
.fail(function () {
|
||||
$tableUsers.html('<tr><td colspan="2" class="text-center text-muted py-6">Gagal memuat data</td></tr>');
|
||||
});
|
||||
};
|
||||
|
||||
$filterForm.on('submit', function (e) {
|
||||
e.preventDefault();
|
||||
const href = `${window.location.pathname}?${$filterForm.serialize()}`;
|
||||
loadPage(href, true);
|
||||
});
|
||||
|
||||
$(document).on('click', '#progressPager a', function (e) {
|
||||
e.preventDefault();
|
||||
loadPage($(this).attr('href'), true);
|
||||
});
|
||||
|
||||
window.addEventListener('popstate', function () {
|
||||
loadPage(window.location.href, false);
|
||||
});
|
||||
|
||||
const renderDetail = (rows) => {
|
||||
if (!rows.length) {
|
||||
$('#detailTable').html('<tr><td colspan="4" class="text-center text-muted py-6">Belum ada data</td></tr>');
|
||||
return;
|
||||
}
|
||||
|
||||
const html = rows
|
||||
.map((r) => {
|
||||
const status = String(r.status ?? '-');
|
||||
const badge =
|
||||
status === 'lulus'
|
||||
? '<span class="badge badge-light-success">Lulus</span>'
|
||||
: status === 'tidak_lulus'
|
||||
? '<span class="badge badge-light-danger">Tidak Lulus</span>'
|
||||
: `<span class="badge badge-light">${status}</span>`;
|
||||
|
||||
const waktu = r.created_at ? String(r.created_at) : '-';
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td>${escapeHtml(r.masterpitstop_id ?? '-')}</td>
|
||||
<td>${escapeHtml(r.step_nama ?? '-')}</td>
|
||||
<td>${badge}</td>
|
||||
<td class="text-end">${escapeHtml(waktu)}</td>
|
||||
</tr>`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
$('#detailTable').html(html);
|
||||
};
|
||||
|
||||
$(document).on('click', '.viewDetail', function (e) {
|
||||
e.preventDefault();
|
||||
const id = $(this).data('id');
|
||||
const nama = $(this).data('nama');
|
||||
|
||||
$('#detailNama').text(nama);
|
||||
$('#detailTable').html('<tr><td colspan="4" class="text-center text-muted py-6">Memuat data...</td></tr>');
|
||||
|
||||
$.get('/pitstop/progress-detail', { pegawai_id: id })
|
||||
.done(function (res) {
|
||||
renderDetail(res?.data ?? []);
|
||||
})
|
||||
.fail(function () {
|
||||
$('#detailTable').html('<tr><td colspan="4" class="text-center text-muted py-6">Gagal memuat data</td></tr>');
|
||||
});
|
||||
|
||||
new bootstrap.Modal(document.getElementById('modalDetail')).show();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
278
resources/views/pitstop/progress_external_detail.blade.php
Normal file
278
resources/views/pitstop/progress_external_detail.blade.php
Normal file
@ -0,0 +1,278 @@
|
||||
@extends('partials.main')
|
||||
|
||||
@section('content')
|
||||
<div class="card card-flush mb-7">
|
||||
<div class="card-header pt-7">
|
||||
<div class="card-title">
|
||||
<div class="d-flex flex-column">
|
||||
<h2 class="mb-0 fw-bold">{{ $tipe ?? '-' }}</h2>
|
||||
<div class="text-muted">Detail monitoring karyawan luar per tipe</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-toolbar">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-light-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fa-solid fa-file-export me-2"></i>Export
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li><h6 class="dropdown-header">Tipe</h6></li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="/monitoring-pra-akreditasi/pdf-external?tipe={{ urlencode($tipe ?? '') }}">PDF</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="/monitoring-pra-akreditasi/excel-external?tipe={{ urlencode($tipe ?? '') }}">Excel</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<a href="/" class="btn btn-sm btn-light">
|
||||
<i class="fa-solid fa-arrow-left me-2"></i>Kembali
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body pt-3">
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-4">
|
||||
<div class="d-flex align-items-center position-relative">
|
||||
<span class="svg-icon svg-icon-1 position-absolute ms-4">
|
||||
<i class="fa-solid fa-magnifying-glass"></i>
|
||||
</span>
|
||||
<input type="text" id="searchPegawaiLuar" class="form-control form-control-solid w-300px ps-12" placeholder="Cari karyawan..." autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table id="tblPegawaiLuar" class="table align-middle table-row-dashed fs-6 gy-3 w-100">
|
||||
<thead>
|
||||
<tr class="text-muted fw-semibold fs-7 text-uppercase gs-0">
|
||||
<th>Nama</th>
|
||||
<th style="width: 320px">Progress</th>
|
||||
<th>Belum dikerjakan</th>
|
||||
<th class="text-end">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="fw-semibold text-gray-800"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
<div class="modal fade" id="modalDetailLuar" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Detail Progress</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="text-muted mb-4" id="detailNamaLuar"></div>
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle table-row-dashed fs-6 gy-3">
|
||||
<thead>
|
||||
<tr class="text-muted fw-semibold fs-7 text-uppercase gs-0">
|
||||
<th>Nama Step</th>
|
||||
<th>Status</th>
|
||||
<th class="text-end">Waktu</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="fw-semibold text-gray-800" id="detailTableLuar">
|
||||
<tr>
|
||||
<td colspan="4" class="text-center text-muted py-6">Memuat data...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modalBelumLuar" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">PitStop Belum Dikerjakan</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="fw-semibold mb-4 text-dark" id="belumNamaLuar"></div>
|
||||
<ul id="belumListLuar" class="mb-0 ps-5"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@section('custom_js')
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
const tipe = String(@json($tipe ?? ''));
|
||||
const totalSteps = Number(@json((int) ($totalSteps ?? 0)));
|
||||
const csrf = $('meta[name="csrf-token"]').attr('content');
|
||||
|
||||
const escapeHtml = (str) => String(str ?? '').replace(/[&<>"']/g, (m) => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
}[m]));
|
||||
|
||||
const renderDetail = (rows) => {
|
||||
if (!rows.length) {
|
||||
$('#detailTableLuar').html('<tr><td colspan="4" class="text-center text-muted py-6">Belum ada data</td></tr>');
|
||||
return;
|
||||
}
|
||||
|
||||
const html = rows
|
||||
.map((r) => {
|
||||
const status = String(r.status ?? '-');
|
||||
const badge =
|
||||
status === 'lulus'
|
||||
? '<span class="badge badge-light-success">Lulus</span>'
|
||||
: status === 'tidak_lulus'
|
||||
? '<span class="badge badge-light-danger">Tidak Lulus</span>'
|
||||
: `<span class="badge badge-light">${escapeHtml(status)}</span>`;
|
||||
|
||||
const waktu = r.created_at ? String(r.created_at) : '-';
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td>${escapeHtml(r.step_nama ?? '-')}</td>
|
||||
<td>${badge}</td>
|
||||
<td class="text-end">${escapeHtml(waktu)}</td>
|
||||
</tr>`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
$('#detailTableLuar').html(html);
|
||||
};
|
||||
|
||||
|
||||
$('#step_luar').on('change', function () {
|
||||
const selectedLocked = $('#step_luar').find('option:selected').attr('data-locked') === '1';
|
||||
$('#stepHintLuar').html(selectedLocked ? '<span class="text-success fw-semibold">Selesai</span>' : '');
|
||||
});
|
||||
|
||||
$('#tblPegawaiLuar').DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
searchDelay: 350,
|
||||
dom: 'rtip',
|
||||
pageLength: 10,
|
||||
lengthMenu: [10, 20, 50, 100],
|
||||
order: [[2, 'desc']],
|
||||
ajax: {
|
||||
url: `/data/progress-external/${encodeURIComponent(tipe)}`,
|
||||
type: 'GET',
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
data: null,
|
||||
render: function (data, type, row) {
|
||||
const nama = escapeHtml(row.nama ?? '-');
|
||||
const nik = escapeHtml(row.nik ?? '-');
|
||||
return `
|
||||
<div class="d-flex flex-column">
|
||||
<span class="fw-semibold">${nama}</span>
|
||||
<span class="text-muted fs-7">NIK: ${nik}</span>
|
||||
</div>`;
|
||||
},
|
||||
},
|
||||
{
|
||||
data: null,
|
||||
orderable: false,
|
||||
render: function (data, type, row) {
|
||||
const pct = Number(row.pct ?? 0);
|
||||
return `
|
||||
<div class="d-flex flex-column gap-1">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="text-muted fs-7">${row.lulus_count ?? 0} / ${totalSteps}</span>
|
||||
<span class="text-muted fs-7">${totalSteps > 0 ? pct + '%' : '-'}</span>
|
||||
</div>
|
||||
<div class="progress h-6px">
|
||||
<div class="progress-bar bg-primary" role="progressbar" style="width: ${pct}%" aria-valuenow="${pct}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>`;
|
||||
},
|
||||
},
|
||||
{
|
||||
data: null,
|
||||
className: 'text-center',
|
||||
orderable: false,
|
||||
render: function (data, type, row) {
|
||||
const c = Number(row.belum_count ?? 0);
|
||||
if (c <= 0) return '<span class="text-muted">-</span>';
|
||||
return `<a href="#" class="showBelumLuar" data-nama="${escapeHtml(row.nama)}" data-steps="${escapeHtml(row.belum_steps ?? '')}" data-nik="${escapeHtml(row.nik ?? '')}"><span class="badge badge-warning text-dark">${c} pitstop</span></a>`;
|
||||
},
|
||||
},
|
||||
{
|
||||
data: null,
|
||||
className: 'text-end',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
render: function (data, type, row) {
|
||||
const disabled = Number(row.selesai ?? 0) === 1 ? 'disabled' : '';
|
||||
return `
|
||||
<div class="d-flex justify-content-end gap-2">
|
||||
<a href="#" class="btn btn-sm btn-primary viewDetailLuar" data-id="${row.id}" data-nama="${escapeHtml(row.nama)}" data-nik="${escapeHtml(row.nik ?? '-')}">Detail</a>
|
||||
</div>`;
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const table = $('#tblPegawaiLuar').DataTable();
|
||||
let timer = null;
|
||||
$('#searchPegawaiLuar').on('keyup', function () {
|
||||
const val = String($(this).val() ?? '');
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(function () {
|
||||
table.search(val).draw();
|
||||
}, 250);
|
||||
});
|
||||
|
||||
$(document).on('click', '.viewDetailLuar', function (e) {
|
||||
e.preventDefault();
|
||||
const id = $(this).data('id');
|
||||
const nama = $(this).data('nama');
|
||||
const nik = $(this).data('nik');
|
||||
|
||||
$('#detailNamaLuar').text(`${nama} (NIK: ${nik})`);
|
||||
$('#detailTableLuar').html('<tr><td colspan="4" class="text-center text-muted py-6">Memuat data...</td></tr>');
|
||||
|
||||
$.get('/pitstop/progress-detail-external', { pegawai_id: id })
|
||||
.done(function (res) {
|
||||
renderDetail(res?.data ?? []);
|
||||
})
|
||||
.fail(function () {
|
||||
$('#detailTableLuar').html('<tr><td colspan="4" class="text-center text-muted py-6">Gagal memuat data</td></tr>');
|
||||
});
|
||||
|
||||
new bootstrap.Modal(document.getElementById('modalDetailLuar')).show();
|
||||
});
|
||||
|
||||
$(document).on('click', '.showBelumLuar', function (e) {
|
||||
e.preventDefault();
|
||||
const nama = $(this).data('nama');
|
||||
const nik = $(this).data('nik');
|
||||
const stepsRaw = String($(this).data('steps') ?? '').trim();
|
||||
|
||||
$('#belumNamaLuar').text(nama + ' (NIK ' + nik +')');
|
||||
|
||||
if (!stepsRaw) {
|
||||
$('#belumListLuar').html('<li class="text-muted">Tidak ada data.</li>');
|
||||
} else {
|
||||
const steps = stepsRaw.split(',').map((s) => s.trim()).filter(Boolean);
|
||||
const html = steps.map((s) => `<li><span class="fw-semibold">${escapeHtml(s)}</span></li>`).join('');
|
||||
$('#belumListLuar').html(html || '<li class="text-muted">Tidak ada data.</li>');
|
||||
}
|
||||
|
||||
new bootstrap.Modal(document.getElementById('modalBelumLuar')).show();
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
506
resources/views/pitstop/progress_unit.blade.php
Normal file
506
resources/views/pitstop/progress_unit.blade.php
Normal file
@ -0,0 +1,506 @@
|
||||
@extends('partials.main')
|
||||
|
||||
@section('content')
|
||||
<div class="card card-flush">
|
||||
<div class="card-header pt-7">
|
||||
<div class="card-title">
|
||||
<h2 class="mb-0 fw-bold">Monitoring Pra Akreditasi</h2>
|
||||
</div>
|
||||
<div class="card-toolbar">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-light-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fa-solid fa-file-export me-2"></i>Export
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li>
|
||||
<h6 class="dropdown-header">PDF</h6>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="/monitoring-pra-akreditasi/pdf">Internal</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="/monitoring-pra-akreditasi/pdf-external">External</a>
|
||||
</li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li>
|
||||
<h6 class="dropdown-header">Excel</h6>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="/monitoring-pra-akreditasi/excel">Internal</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="/monitoring-pra-akreditasi/excel-external">External</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body pt-3">
|
||||
<ul class="nav nav-tabs nav-line-tabs nav-line-tabs-2x mb-5 fs-6" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link fw-semibold active" data-bs-toggle="tab" href="#tabAllKaryawan" role="tab" aria-selected="true">Semua Karyawan</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link fw-semibold" data-bs-toggle="tab" href="#tabInternal" role="tab" aria-selected="false">Unit Internal</a>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<a class="nav-link fw-semibold" data-bs-toggle="tab" href="#tabExternal" role="tab" aria-selected="false">Unit Eksternal</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="tabAllKaryawan" role="tabpanel">
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-4">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="text-muted">Daftar gabungan internal dan karyawan luar</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center position-relative">
|
||||
<span class="svg-icon svg-icon-1 position-absolute ms-4">
|
||||
<i class="fa-solid fa-magnifying-glass"></i>
|
||||
</span>
|
||||
<input type="text" id="searchAllKaryawan" class="form-control form-control-solid w-300px ps-12" placeholder="Cari nama / NIP / NIK / unit..." autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table id="tblAllKaryawan" class="table align-middle table-row-dashed fs-6 gy-3 w-100">
|
||||
<thead>
|
||||
<tr class="text-muted fw-semibold fs-7 text-uppercase gs-0">
|
||||
<th>Nama</th>
|
||||
<th>Unit / Tipe</th>
|
||||
<th style="width: 320px">Progress</th>
|
||||
<th>Belum dikerjakan</th>
|
||||
<th class="text-end">Jenis</th>
|
||||
<th class="text-end">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="fw-semibold text-gray-800"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="tabInternal" role="tabpanel">
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-4">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="text-muted">Ringkasan monitoring per unit</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center position-relative">
|
||||
<span class="svg-icon svg-icon-1 position-absolute ms-4">
|
||||
<i class="fa-solid fa-magnifying-glass"></i>
|
||||
</span>
|
||||
<input type="text" id="searchUnit" class="form-control form-control-solid w-300px ps-12" placeholder="Cari unit..." autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table id="tblUnit" class="table align-middle table-row-dashed fs-6 gy-3 w-100">
|
||||
<thead>
|
||||
<tr class="text-muted fw-semibold fs-7 text-uppercase gs-0">
|
||||
<th>Unit</th>
|
||||
<th class="text-end">Total Pegawai</th>
|
||||
<th style="width: 260px">Total Selesai</th>
|
||||
<th class="text-end">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="fw-semibold text-gray-800"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="tabExternal" role="tabpanel">
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-4">
|
||||
<div class="d-flex flex-column">
|
||||
<div class="text-muted">Ringkasan monitoring per tipe</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center position-relative">
|
||||
<span class="svg-icon svg-icon-1 position-absolute ms-4">
|
||||
<i class="fa-solid fa-magnifying-glass"></i>
|
||||
</span>
|
||||
<input type="text" id="searchExternal" class="form-control form-control-solid w-300px ps-12" placeholder="Cari tipe..." autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table id="tblExternal" class="table align-middle table-row-dashed fs-6 gy-3 w-100">
|
||||
<thead>
|
||||
<tr class="text-muted fw-semibold fs-7 text-uppercase gs-0">
|
||||
<th>Tipe</th>
|
||||
<th class="text-end">Total Karyawan</th>
|
||||
<th style="width: 260px">Total Selesai</th>
|
||||
<th class="text-end">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="fw-semibold text-gray-800"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
||||
<div class="modal fade" id="modalAllDetail" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Detail Progress</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="text-muted mb-4" id="allDetailNama"></div>
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle table-row-dashed fs-6 gy-3">
|
||||
<thead>
|
||||
<tr class="text-muted fw-semibold fs-7 text-uppercase gs-0">
|
||||
<th>Nama Step</th>
|
||||
<th>Status</th>
|
||||
<th class="text-end">Waktu</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="fw-semibold text-gray-800" id="allDetailTable">
|
||||
<tr>
|
||||
<td colspan="4" class="text-center text-muted py-6">Memuat data...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modalAllBelum" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">PitStop Belum Dikerjakan</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="text-dark fw-semibold mb-4" id="allBelumNama"></div>
|
||||
<ul id="allBelumList" class="mb-0 ps-5"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section('custom_js')
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
const totalSteps = Number(@json((int) ($totalSteps ?? 0)));
|
||||
|
||||
const escapeHtml = (str) => String(str ?? '').replace(/[&<>"']/g, (m) => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
}[m]));
|
||||
|
||||
const renderDetailRows = (rows) => {
|
||||
if (!rows.length) {
|
||||
$('#allDetailTable').html('<tr><td colspan="4" class="text-center text-muted py-6">Belum ada data</td></tr>');
|
||||
return;
|
||||
}
|
||||
|
||||
const html = rows
|
||||
.map((r) => {
|
||||
const status = String(r.status ?? '-');
|
||||
const badge =
|
||||
status === 'lulus'
|
||||
? '<span class="badge badge-light-success">Lulus</span>'
|
||||
: status === 'tidak_lulus'
|
||||
? '<span class="badge badge-light-danger">Tidak Lulus</span>'
|
||||
: `<span class="badge badge-light">${escapeHtml(status)}</span>`;
|
||||
|
||||
const waktu = r.created_at ? String(r.created_at) : '-';
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td>${escapeHtml(r.step_nama ?? '-')}</td>
|
||||
<td>${badge}</td>
|
||||
<td class="text-end">${escapeHtml(waktu)}</td>
|
||||
</tr>`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
$('#allDetailTable').html(html);
|
||||
};
|
||||
|
||||
$('#tblUnit').DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
searchDelay: 350,
|
||||
dom: 'rtip',
|
||||
pageLength: 10,
|
||||
lengthMenu: [10, 20, 50, 100],
|
||||
order: [[2, 'desc']],
|
||||
ajax: {
|
||||
url: '/data/progress-Internal',
|
||||
type: 'GET',
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
data: 'unit_name',
|
||||
render: function (data, type, row) {
|
||||
const name = escapeHtml(data ?? '-');
|
||||
return `<div class="d-flex flex-column"><span class="fw-bold">${name}</span></div>`;
|
||||
},
|
||||
},
|
||||
{ data: 'total_pegawai', className: 'text-end' },
|
||||
{
|
||||
data: null,
|
||||
orderable: false,
|
||||
render: function (data, type, row) {
|
||||
const pct = Number(row.selesai_pct ?? 0);
|
||||
const label = `${pct}%`;
|
||||
return `
|
||||
<div class="d-flex flex-column gap-1">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="text-muted fs-7">${row.pegawai_selesai ?? 0} / ${row.total_pegawai ?? 0} pegawai</span>
|
||||
<span class="text-muted fs-7">${label}</span>
|
||||
</div>
|
||||
<div class="progress h-6px">
|
||||
<div class="progress-bar bg-primary" role="progressbar" style="width: ${pct}%" aria-valuenow="${pct}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>`;
|
||||
},
|
||||
},
|
||||
{
|
||||
data: null,
|
||||
className: 'text-end',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
render: function (data, type, row) {
|
||||
return `<a class="btn btn-sm btn-light-primary" href="/pitstop/progress-unit/${row.unit_id}">Detail</a>`;
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const table = $('#tblUnit').DataTable();
|
||||
let timer = null;
|
||||
$('#searchUnit').on('keyup', function () {
|
||||
const val = String($(this).val() ?? '');
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(function () {
|
||||
table.search(val).draw();
|
||||
}, 250);
|
||||
});
|
||||
|
||||
$('#tblExternal').DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
searchDelay: 350,
|
||||
dom: 'rtip',
|
||||
pageLength: 10,
|
||||
lengthMenu: [10, 20, 50, 100],
|
||||
order: [[2, 'desc']],
|
||||
ajax: {
|
||||
url: '/data/progress-external',
|
||||
type: 'GET',
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
data: 'tipe',
|
||||
render: function (data) {
|
||||
const name = escapeHtml(data ?? '-');
|
||||
return `<div class="d-flex flex-column"><span class="fw-bold">${name}</span></div>`;
|
||||
},
|
||||
},
|
||||
{ data: 'total_pegawai', className: 'text-end' },
|
||||
{
|
||||
data: null,
|
||||
orderable: false,
|
||||
render: function (data, type, row) {
|
||||
const pct = Number(row.selesai_pct ?? 0);
|
||||
const label = `${pct}%`;
|
||||
return `
|
||||
<div class="d-flex flex-column gap-1">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="text-muted fs-7">${row.pegawai_selesai ?? 0} / ${row.total_pegawai ?? 0} karyawan</span>
|
||||
<span class="text-muted fs-7">${label}</span>
|
||||
</div>
|
||||
<div class="progress h-6px">
|
||||
<div class="progress-bar bg-primary" role="progressbar" style="width: ${pct}%" aria-valuenow="${pct}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>`;
|
||||
},
|
||||
},
|
||||
{
|
||||
data: null,
|
||||
className: 'text-end',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
render: function (data, type, row) {
|
||||
const tipe = escapeHtml(row.tipe ?? '-');
|
||||
return `<a class="btn btn-sm btn-light-primary" href="/pitstop/progress-external/${encodeURIComponent(tipe)}">Detail</a>`;
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const extTable = $('#tblExternal').DataTable();
|
||||
let extTimer = null;
|
||||
$('#searchExternal').on('keyup', function () {
|
||||
const val = String($(this).val() ?? '');
|
||||
clearTimeout(extTimer);
|
||||
extTimer = setTimeout(function () {
|
||||
extTable.search(val).draw();
|
||||
}, 250);
|
||||
});
|
||||
|
||||
$('#tblAllKaryawan').DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
searchDelay: 350,
|
||||
dom: 'rtip',
|
||||
pageLength: 10,
|
||||
lengthMenu: [10, 20, 50, 100],
|
||||
order: [[2, 'desc']],
|
||||
ajax: {
|
||||
url: '/data/progress-all-karyawan',
|
||||
type: 'GET',
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
data: null,
|
||||
render: function (data, type, row) {
|
||||
const nama = escapeHtml(row.nama ?? '-');
|
||||
const idt = escapeHtml(row.identitas ?? '-');
|
||||
const label = row.tipe_karyawan === 'luar' ? 'NIK' : 'NIP';
|
||||
return `
|
||||
<div class="d-flex flex-column">
|
||||
<span class="fw-semibold">${nama}</span>
|
||||
<span class="text-muted fs-7">${label}: ${idt}</span>
|
||||
</div>`;
|
||||
},
|
||||
},
|
||||
{
|
||||
data: 'unit_name',
|
||||
render: function (data) {
|
||||
return `<span class="fw-semibold">${escapeHtml(data ?? '-')}</span>`;
|
||||
},
|
||||
},
|
||||
{
|
||||
data: null,
|
||||
orderable: false,
|
||||
render: function (data, type, row) {
|
||||
const pct = Number(row.pct ?? 0);
|
||||
return `
|
||||
<div class="d-flex flex-column gap-1">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="text-muted fs-7">${row.lulus_count ?? 0} / ${totalSteps}</span>
|
||||
<span class="text-muted fs-7">${totalSteps > 0 ? pct + '%' : '-'}</span>
|
||||
</div>
|
||||
<div class="progress h-6px">
|
||||
<div class="progress-bar bg-primary" role="progressbar" style="width: ${pct}%" aria-valuenow="${pct}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>`;
|
||||
},
|
||||
},
|
||||
{
|
||||
data: null,
|
||||
className: 'text-center',
|
||||
orderable: false,
|
||||
render: function (data, type, row) {
|
||||
const c = Number(row.belum_count ?? 0);
|
||||
if (c <= 0) return '<span class="text-muted">-</span>';
|
||||
const nama = escapeHtml(row.nama ?? '-');
|
||||
const idt = escapeHtml(row.identitas ?? '-');
|
||||
const label = row.tipe_karyawan === 'luar' ? 'NIK' : 'NIP';
|
||||
return `<a href="#" class="showBelumAll" data-nama="${nama}" data-identitas="${idt}" data-label="${label}" data-steps="${escapeHtml(row.belum_steps ?? '')}"><span class="badge badge-warning text-dark">${c} pitstop</span></a>`;
|
||||
},
|
||||
},
|
||||
{
|
||||
data: 'tipe_karyawan',
|
||||
className: 'text-end',
|
||||
render: function (data) {
|
||||
const tipe = String(data ?? 'internal');
|
||||
return tipe === 'luar'
|
||||
? '<span class="badge badge-light-warning text-dark">External</span>'
|
||||
: '<span class="badge badge-light-primary">Internal</span>';
|
||||
},
|
||||
},
|
||||
{
|
||||
data: null,
|
||||
className: 'text-end',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
render: function (data, type, row) {
|
||||
const tipe = String(row.tipe_karyawan ?? 'internal');
|
||||
const nama = escapeHtml(row.nama ?? '-');
|
||||
const identitas = escapeHtml(row.identitas ?? '-');
|
||||
return `<a href="#" class="btn btn-sm btn-primary viewDetailAll" data-id="${row.id}" data-tipe="${escapeHtml(tipe)}" data-nama="${nama}" data-identitas="${identitas}">Detail</a>`;
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const allTable = $('#tblAllKaryawan').DataTable();
|
||||
let allTimer = null;
|
||||
$('#searchAllKaryawan').on('keyup', function () {
|
||||
const val = String($(this).val() ?? '');
|
||||
clearTimeout(allTimer);
|
||||
allTimer = setTimeout(function () {
|
||||
allTable.search(val).draw();
|
||||
}, 250);
|
||||
});
|
||||
|
||||
$(document).on('click', '.viewDetailAll', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const id = Number($(this).data('id'));
|
||||
const tipe = String($(this).data('tipe') ?? 'internal');
|
||||
const nama = String($(this).data('nama') ?? '-');
|
||||
const identitas = String($(this).data('identitas') ?? '-');
|
||||
const label = tipe === 'luar' ? 'NIK' : 'NIP';
|
||||
|
||||
$('#allDetailNama').text(`${nama} (${label}: ${identitas})`);
|
||||
$('#allDetailTable').html('<tr><td colspan="4" class="text-center text-muted py-6">Memuat data...</td></tr>');
|
||||
|
||||
const url = tipe === 'luar' ? '/pitstop/progress-detail-external' : '/pitstop/progress-detail';
|
||||
$.get(url, { pegawai_id: id })
|
||||
.done(function (res) {
|
||||
renderDetailRows(res?.data ?? []);
|
||||
})
|
||||
.fail(function () {
|
||||
$('#allDetailTable').html('<tr><td colspan="4" class="text-center text-muted py-6">Gagal memuat data</td></tr>');
|
||||
});
|
||||
|
||||
new bootstrap.Modal(document.getElementById('modalAllDetail')).show();
|
||||
});
|
||||
|
||||
$(document).on('click', '.showBelumAll', function (e) {
|
||||
e.preventDefault();
|
||||
const nama = String($(this).data('nama') ?? '-');
|
||||
const identitas = String($(this).data('identitas') ?? '-');
|
||||
const label = String($(this).data('label') ?? '-');
|
||||
const stepsRaw = String($(this).data('steps') ?? '').trim();
|
||||
|
||||
$('#allBelumNama').text(`${nama} (${label}: ${identitas})`);
|
||||
|
||||
if (!stepsRaw) {
|
||||
$('#allBelumList').html('<li class="text-muted">Tidak ada data.</li>');
|
||||
} else {
|
||||
const steps = stepsRaw.split(',').map((s) => s.trim()).filter(Boolean);
|
||||
const html = steps.map((s) => `<li><span class="fw-semibold">${escapeHtml(s)}</span></li>`).join('');
|
||||
$('#allBelumList').html(html || '<li class="text-muted">Tidak ada data.</li>');
|
||||
}
|
||||
|
||||
new bootstrap.Modal(document.getElementById('modalAllBelum')).show();
|
||||
});
|
||||
|
||||
$('a[data-bs-toggle="tab"]').on('shown.bs.tab', function () {
|
||||
table.columns.adjust();
|
||||
extTable.columns.adjust();
|
||||
allTable.columns.adjust();
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
266
resources/views/pitstop/progress_unit_detail.blade.php
Normal file
266
resources/views/pitstop/progress_unit_detail.blade.php
Normal file
@ -0,0 +1,266 @@
|
||||
@extends('partials.main')
|
||||
|
||||
@section('content')
|
||||
<div class="card card-flush mb-7">
|
||||
<div class="card-header pt-7">
|
||||
<div class="card-title">
|
||||
<div class="d-flex flex-column">
|
||||
<h2 class="mb-0 fw-bold">{{ $unit->name ?? 'Unit' }}</h2>
|
||||
<div class="text-muted">Detail monitoring pegawai per unit</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-toolbar">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-light-primary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<i class="fa-solid fa-file-export me-2"></i>Export
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li><h6 class="dropdown-header">Unit</h6></li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="/monitoring-pra-akreditasi/pdf?unit_id={{ (int) ($unit->id ?? 0) }}">PDF</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="/monitoring-pra-akreditasi/excel?unit_id={{ (int) ($unit->id ?? 0) }}">Excel</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<a href="/" class="btn btn-sm btn-light">
|
||||
<i class="fa-solid fa-arrow-left me-2"></i>Kembali
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body pt-3">
|
||||
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-4">
|
||||
<div class="d-flex align-items-center position-relative">
|
||||
<span class="svg-icon svg-icon-1 position-absolute ms-4">
|
||||
<i class="fa-solid fa-magnifying-glass"></i>
|
||||
</span>
|
||||
<input type="text" id="searchPegawai" class="form-control form-control-solid w-300px ps-12" placeholder="Cari pegawai..." autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table id="tblPegawai" class="table align-middle table-row-dashed fs-6 gy-3 w-100">
|
||||
<thead>
|
||||
<tr class="text-muted fw-semibold fs-7 text-uppercase gs-0">
|
||||
<th>Nama</th>
|
||||
<th style="width: 320px">Progress</th>
|
||||
<th>Belum dikerjakan</th>
|
||||
<th class="text-end">Aksi</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="fw-semibold text-gray-800"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
<div class="modal fade" id="modalDetail" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Detail Progress</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="text-muted mb-4" id="detailNama"></div>
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle table-row-dashed fs-6 gy-3">
|
||||
<thead>
|
||||
<tr class="text-muted fw-semibold fs-7 text-uppercase gs-0">
|
||||
<th>Nama Step</th>
|
||||
<th>Status</th>
|
||||
<th class="text-end">Waktu</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="fw-semibold text-gray-800" id="detailTable">
|
||||
<tr>
|
||||
<td colspan="4" class="text-center text-muted py-6">Memuat data...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="modalBelum" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">PitStop Belum Dikerjakan</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="text-dark fw-semibold mb-4" id="belumNama"></div>
|
||||
<ul id="belumList" class="mb-0 ps-5"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section('custom_js')
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
const unitId = Number(@json((int) ($unit->id ?? 0)));
|
||||
const totalSteps = Number(@json((int) ($totalSteps ?? 0)));
|
||||
|
||||
const escapeHtml = (str) => String(str ?? '').replace(/[&<>"']/g, (m) => ({
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
}[m]));
|
||||
|
||||
const renderDetail = (rows) => {
|
||||
if (!rows.length) {
|
||||
$('#detailTable').html('<tr><td colspan="4" class="text-center text-muted py-6">Belum ada data</td></tr>');
|
||||
return;
|
||||
}
|
||||
|
||||
const html = rows
|
||||
.map((r) => {
|
||||
const status = String(r.status ?? '-');
|
||||
const badge =
|
||||
status === 'lulus'
|
||||
? '<span class="badge badge-light-success">Lulus</span>'
|
||||
: status === 'tidak_lulus'
|
||||
? '<span class="badge badge-light-danger">Tidak Lulus</span>'
|
||||
: `<span class="badge badge-light">${escapeHtml(status)}</span>`;
|
||||
|
||||
const waktu = r.created_at ? String(r.created_at) : '-';
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td>${escapeHtml(r.step_nama ?? '-')}</td>
|
||||
<td>${badge}</td>
|
||||
<td class="text-end">${escapeHtml(waktu)}</td>
|
||||
</tr>`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
$('#detailTable').html(html);
|
||||
};
|
||||
|
||||
$('#tblPegawai').DataTable({
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
searchDelay: 350,
|
||||
dom: 'rtip',
|
||||
pageLength: 10,
|
||||
lengthMenu: [10, 20, 50, 100],
|
||||
order: [[2, 'desc']],
|
||||
ajax: {
|
||||
url: `/data/progress-Internal/${unitId}`,
|
||||
type: 'GET',
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
data: null,
|
||||
render: function (data, type, row) {
|
||||
const nama = escapeHtml(row.nama ?? '-');
|
||||
const nip = escapeHtml(row.nip_pns ?? '-');
|
||||
return `
|
||||
<div class="d-flex flex-column">
|
||||
<span class="fw-semibold">${nama}</span>
|
||||
<span class="text-muted fs-7">NIP: ${nip}</span>
|
||||
</div>`;
|
||||
},
|
||||
},
|
||||
{
|
||||
data: null,
|
||||
orderable: false,
|
||||
render: function (data, type, row) {
|
||||
const pct = Number(row.pct ?? 0);
|
||||
return `
|
||||
<div class="d-flex flex-column gap-1">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="text-muted fs-7">${row.lulus_count ?? 0} / ${totalSteps}</span>
|
||||
<span class="text-muted fs-7">${totalSteps > 0 ? pct + '%' : '-'}</span>
|
||||
</div>
|
||||
<div class="progress h-6px">
|
||||
<div class="progress-bar bg-primary" role="progressbar" style="width: ${pct}%" aria-valuenow="${pct}" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>`;
|
||||
},
|
||||
},
|
||||
{
|
||||
data: null,
|
||||
className: 'text-center',
|
||||
orderable: false,
|
||||
render: function (data, type, row) {
|
||||
const c = Number(row.belum_count ?? 0);
|
||||
if (c <= 0) return '<span class="text-muted">-</span>';
|
||||
return `<a href="#" class="showBelum" data-nama="${escapeHtml(row.nama)}" data-steps="${escapeHtml(row.belum_steps ?? '')}" data-nip="${escapeHtml(row.nip_pns ?? '')}"><span class="badge badge-warning text-dark">${c} pitstop</span></a>`;
|
||||
},
|
||||
},
|
||||
{
|
||||
data: null,
|
||||
className: 'text-end',
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
render: function (data, type, row) {
|
||||
return `<a href="#" class="btn btn-sm btn-primary viewDetail" data-id="${row.id}" data-nama="${escapeHtml(row.nama)}" data-nip="${escapeHtml(row.nip_pns ?? '-')}">Detail</a>`;
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const table = $('#tblPegawai').DataTable();
|
||||
let timer = null;
|
||||
$('#searchPegawai').on('keyup', function () {
|
||||
const val = String($(this).val() ?? '');
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(function () {
|
||||
table.search(val).draw();
|
||||
}, 250);
|
||||
});
|
||||
|
||||
$(document).on('click', '.viewDetail', function (e) {
|
||||
e.preventDefault();
|
||||
const id = $(this).data('id');
|
||||
const nama = $(this).data('nama');
|
||||
const nip = $(this).data('nip');
|
||||
|
||||
$('#detailNama').text(`${nama} (NIP: ${nip})`);
|
||||
$('#detailTable').html('<tr><td colspan="4" class="text-center text-muted py-6">Memuat data...</td></tr>');
|
||||
|
||||
$.get('/pitstop/progress-detail', { pegawai_id: id })
|
||||
.done(function (res) {
|
||||
renderDetail(res?.data ?? []);
|
||||
})
|
||||
.fail(function () {
|
||||
$('#detailTable').html('<tr><td colspan="4" class="text-center text-muted py-6">Gagal memuat data</td></tr>');
|
||||
});
|
||||
|
||||
new bootstrap.Modal(document.getElementById('modalDetail')).show();
|
||||
});
|
||||
|
||||
$(document).on('click', '.showBelum', function (e) {
|
||||
e.preventDefault();
|
||||
const nama = $(this).data('nama');
|
||||
const nip = $(this).data('nip');
|
||||
const stepsRaw = String($(this).data('steps') ?? '').trim();
|
||||
console.log(nip);
|
||||
|
||||
$('#belumNama').text(nama + ' (NIP ' + nip +')');
|
||||
|
||||
if (!stepsRaw) {
|
||||
$('#belumList').html('<li class="text-muted">Tidak ada data.</li>');
|
||||
} else {
|
||||
const steps = stepsRaw.split(',').map((s) => s.trim()).filter(Boolean);
|
||||
const html = steps.map((s) => `<li><span class="fw-semibold">${escapeHtml(s)}</span></li>`).join('');
|
||||
$('#belumList').html(html || '<li class="text-muted">Tidak ada data.</li>');
|
||||
}
|
||||
|
||||
new bootstrap.Modal(document.getElementById('modalBelum')).show();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@ -3,15 +3,48 @@
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use App\Http\Controllers\KaryawanController;
|
||||
use App\Http\Controllers\PitStopController;
|
||||
use App\Http\Controllers\MasterPitStopController;
|
||||
use App\Http\Controllers\AuthController;
|
||||
|
||||
Route::middleware(['auth'])->group(function(){
|
||||
|
||||
Route::get('/pitstop/progress-detail', [PitStopController::class, 'progressDetail']);
|
||||
|
||||
Route::get('/list-karyawan', [KaryawanController::class, 'listData']);
|
||||
Route::get('/list-karyawan-luar', [KaryawanController::class, 'listDataKaryawanLuar']);
|
||||
Route::get('/pitstop', [PitStopController::class, 'pitstop']);
|
||||
|
||||
// Progress by unit (DataTables)
|
||||
Route::get('/', [PitStopController::class, 'progressUnit']);
|
||||
Route::get('/monitoring-pra-akreditasi/pdf', [PitStopController::class, 'monitoringPdf']);
|
||||
Route::get('/monitoring-pra-akreditasi/pdf-external', [PitStopController::class, 'monitoringPdfExternal']);
|
||||
Route::get('/monitoring-pra-akreditasi/excel', [PitStopController::class, 'monitoringExcel']);
|
||||
Route::get('/monitoring-pra-akreditasi/excel-external', [PitStopController::class, 'monitoringExcelExternal']);
|
||||
|
||||
Route::get('/pitstop/progress-unit/{unit_id}', [PitStopController::class, 'progressUnitDetail']);
|
||||
Route::get('/pitstop/progress-external/{tipe}', [PitStopController::class, 'progressExternalDetail']);
|
||||
Route::get('/data/progress-Internal', [PitStopController::class, 'dataProgress']);
|
||||
Route::get('/data/progress-Internal/{unit_id}', [PitStopController::class, 'dataProgressUnit']);
|
||||
Route::get('/data/progress-external/{tipe}', [PitStopController::class, 'dataProgressExternalByTipe']);
|
||||
Route::get('/data/progress-all-karyawan', [PitStopController::class, 'dataProgressAllKaryawan']);
|
||||
|
||||
Route::get('/data/progress-external', [PitStopController::class, 'dataProgressExternal']);
|
||||
Route::get('/data/progress-external/detail', [PitStopController::class, 'dataProgressExternalDetail']);
|
||||
|
||||
// Master PitStop (CRUD)
|
||||
Route::get('/master-pitstop', [MasterPitStopController::class, 'index']);
|
||||
Route::get('/master-pitstop/data', [MasterPitStopController::class, 'data']);
|
||||
Route::post('/master-pitstop', [MasterPitStopController::class, 'store']);
|
||||
Route::put('/master-pitstop/{id}', [MasterPitStopController::class, 'update']);
|
||||
Route::patch('/master-pitstop/{id}/toggle', [MasterPitStopController::class, 'toggle']);
|
||||
|
||||
Route::get('/pitstop/pegawai-steps', [PitStopController::class, 'pegawaiSteps']);
|
||||
Route::get('/pitstop/pegawai-steps-external', [PitStopController::class, 'pegawaiStepsExternal']);
|
||||
Route::post('/pitstop/submit', [PitStopController::class, 'submit']);
|
||||
Route::get('/pitstop/progress-detail-external', [PitStopController::class, 'progressDetailExternal']);
|
||||
Route::post('/logout', [AuthController::class, 'logout']);
|
||||
|
||||
Route::get('/', function () {
|
||||
return view('partials.main');
|
||||
});
|
||||
|
||||
|
||||
Route::get('/list-karyawan', [KaryawanController::class, 'listData']);
|
||||
Route::get('/pitstop', [PitStopController::class, 'pitstop']);
|
||||
Route::get('/pitstop/progress', [PitStopController::class, 'progress']);
|
||||
Route::get('/pitstop/progress-detail', [PitStopController::class, 'progressDetail']);
|
||||
Route::get('/pitstop/pegawai-steps', [PitStopController::class, 'pegawaiSteps']);
|
||||
Route::post('/pitstop/submit', [PitStopController::class, 'submit']);
|
||||
Route::get('/login', [AuthController::class, 'login'])->name('login');
|
||||
Route::post('/login', [AuthController::class, 'submitLogin']);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user