From 6689b02fdddccc01469a084b4fe5b01aa84b6a9b Mon Sep 17 00:00:00 2001 From: punkestu <pangestubima89@gmail.com> Date: Mon, 27 Jan 2025 19:45:56 +0700 Subject: [PATCH] update review --- .../API/Rotasi/CabangController.php | 23 ++++ .../Controllers/Rotasi/CabangController.php | 8 ++ app/Models/CabangNode.php | 71 ++++++++++ app/Models/Konsep.php | 5 + ...01_27_110853_create_cabang_nodes_table.php | 29 +++++ resources/views/account/index.blade.php | 3 + resources/views/personel/konsep.blade.php | 2 +- .../views/rotasi/cabang/konsep.blade.php | 22 +++- .../rotasi/cabang/struktur-edit.blade.php | 121 ++++++++++++++++++ .../views/rotasi/cabang/struktur.blade.php | 46 ++++++- resources/views/welcome.blade.php | 2 +- routes/api.php | 3 + routes/web.php | 3 + 13 files changed, 331 insertions(+), 7 deletions(-) create mode 100644 app/Models/CabangNode.php create mode 100644 database/migrations/2025_01_27_110853_create_cabang_nodes_table.php create mode 100644 resources/views/rotasi/cabang/struktur-edit.blade.php diff --git a/app/Http/Controllers/API/Rotasi/CabangController.php b/app/Http/Controllers/API/Rotasi/CabangController.php index b6543a9..0edaa8b 100644 --- a/app/Http/Controllers/API/Rotasi/CabangController.php +++ b/app/Http/Controllers/API/Rotasi/CabangController.php @@ -5,6 +5,7 @@ use Illuminate\Http\Request; use App\Http\Controllers\Controller; use App\Models\Cabang; +use App\Models\CabangNode; class CabangController extends Controller { @@ -57,4 +58,26 @@ public function listInduk() }); return response()->json($cabang); } + public function tree() + { + $tree = CabangNode::getTree(); + return response()->json($tree); + } + public function editNode(Request $request) + { + $request->validate([ + 'cabang_id' => 'required|exists:cabangs,id', + 'root_id' => 'nullable|exists:cabang_nodes,id', + ]); + CabangNode::updateOrCreate( + ['cabang_id' => $request->cabang_id], + ['cabang_id' => $request->cabang_id, 'root_id' => $request->root_id] + ); + return response()->json(['message' => 'success']); + } + public function deleteNode($cabang_id) + { + CabangNode::where('cabang_id', $cabang_id)->delete(); + return response()->json(['message' => 'success']); + } } diff --git a/app/Http/Controllers/Rotasi/CabangController.php b/app/Http/Controllers/Rotasi/CabangController.php index b2768ec..9a65cc3 100644 --- a/app/Http/Controllers/Rotasi/CabangController.php +++ b/app/Http/Controllers/Rotasi/CabangController.php @@ -5,6 +5,7 @@ use Illuminate\Http\Request; use App\Http\Controllers\Controller; use App\Models\Cabang; +use App\Models\CabangNode; use App\Models\Kelas; use App\Models\Konsep; use App\Models\Task; @@ -291,4 +292,11 @@ public function struktur() { return view('rotasi.cabang.struktur'); } + + public function strukturEdit() + { + $nodes = CabangNode::with('cabang')->get(); + $cabangs = Cabang::all(); + return view('rotasi.cabang.struktur-edit', ['nodes' => $nodes, 'cabangs' => $cabangs]); + } } diff --git a/app/Models/CabangNode.php b/app/Models/CabangNode.php new file mode 100644 index 0000000..0c8e3f3 --- /dev/null +++ b/app/Models/CabangNode.php @@ -0,0 +1,71 @@ +<?php + +namespace App\Models; + +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; + +class CabangNode extends Model +{ + use HasFactory; + + protected $fillable = [ + 'cabang_id', + 'root_id', + ]; + + public function cabang() + { + return $this->belongsTo(Cabang::class); + } + + public function root() + { + return $this->belongsTo(CabangNode::class); + } + + public function children() + { + return $this->hasMany(CabangNode::class, 'root_id'); + } + + public static function getTree($rootId = null) + { + if ($rootId === null) { + $roots = CabangNode::with('cabang', 'children')->whereNull('root_id')->get(); + if ($roots->count() == 0) { + return null; + } + + return $roots->map(function ($root) { + return [ + 'id' => (string)$root->id, + 'data' => [ + 'name' => $root->cabang->nama, + ], + 'children' => $root->children->count() > 0 ? $root->children->map(function () use ($root) { + return CabangNode::getTree($root->id); + }) : null, + ]; + }); + } + $root = CabangNode::with('cabang', 'children')->where('root_id', $rootId)->first(); + if (!$root) { + return null; + } + + $result = [ + 'id' => (string)$root->id, + 'data' => [ + 'name' => $root->cabang->nama, + ], + 'children' => $root->children->count() > 0 ? $root->children->map(function () use ($root) { + return CabangNode::getTree($root->id); + }) : null, + ]; + if ($result["children"] === null) { + unset($result["children"]); + } + return $result; + } +} diff --git a/app/Models/Konsep.php b/app/Models/Konsep.php index e611685..9efd644 100644 --- a/app/Models/Konsep.php +++ b/app/Models/Konsep.php @@ -14,4 +14,9 @@ class Konsep extends Model "cabang_id", 'task_id', ]; + + public function task() + { + return $this->belongsTo(Task::class); + } } diff --git a/database/migrations/2025_01_27_110853_create_cabang_nodes_table.php b/database/migrations/2025_01_27_110853_create_cabang_nodes_table.php new file mode 100644 index 0000000..7a50c84 --- /dev/null +++ b/database/migrations/2025_01_27_110853_create_cabang_nodes_table.php @@ -0,0 +1,29 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + */ + public function up(): void + { + Schema::create('cabang_nodes', function (Blueprint $table) { + $table->id(); + $table->foreignId('cabang_id')->constrained('cabangs')->cascadeOnDelete(); + $table->foreignId('root_id')->nullable()->constrained('cabang_nodes')->cascadeOnDelete(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('cabang_nodes'); + } +}; diff --git a/resources/views/account/index.blade.php b/resources/views/account/index.blade.php index 58b947a..51958d1 100644 --- a/resources/views/account/index.blade.php +++ b/resources/views/account/index.blade.php @@ -194,6 +194,9 @@ class="flex items-center gap-1 px-2 py-1 mt-1 bg-gray-200 border-2 border-slate- function tambahJabatan() { const jabatanBaru = document.getElementById('jabatan-baru').value; + if (jabatanBaru === "") { + return; + } const jabatanList = document.getElementById('jabatan-list'); jabatanList.innerHTML += itemJabatan(jabatanBaru); document.getElementById('jabatan-baru').value = ""; diff --git a/resources/views/personel/konsep.blade.php b/resources/views/personel/konsep.blade.php index 21d6046..0e4fdcd 100644 --- a/resources/views/personel/konsep.blade.php +++ b/resources/views/personel/konsep.blade.php @@ -91,7 +91,7 @@ class="bg-[#003285] opacity-80 hover:opacity-100 duration-200 text-white px-2 py d="M18 14v4.833A1.166 1.166 0 0 1 16.833 20H5.167A1.167 1.167 0 0 1 4 18.833V7.167A1.166 1.166 0 0 1 5.167 6h4.618m4.447-2H20v5.768m-7.889 2.121 7.778-7.778" /> </svg> </a> - <iframe class="w-full h-[60vh]" src="{{ $konsep->berkas }}" frameborder="0"></iframe> + {{-- <iframe class="w-full h-[60vh]" src="{{ $konsep->berkas }}" frameborder="0"></iframe> --}} </div> @endforeach </div> diff --git a/resources/views/rotasi/cabang/konsep.blade.php b/resources/views/rotasi/cabang/konsep.blade.php index f1bdb16..78fb2a0 100644 --- a/resources/views/rotasi/cabang/konsep.blade.php +++ b/resources/views/rotasi/cabang/konsep.blade.php @@ -59,9 +59,12 @@ class="resize-none flex-grow p-2 border-2 border-slate-400 rounded-s-md" <select name="task" id="task" class="flex-grow p-2 border-2 border-slate-400 rounded-s-md"> <option value="">Pilih Tugas</option> @foreach ($tasks as $task) - <option value="{{ $task->id }}">{{ $task->deskripsi }}</option> + <option value="{{ $task->id }}" data-doc="{{ $task->berkas }}">{{ $task->deskripsi }} + </option> @endforeach </select> + <a href="" id="doc-url" class="hidden text-blue-500 underline" target="_blank">Lihat + Dokumen</a> <label for="url">Berkas</label> <div class="flex w-full"> <input type="text" name="url" id="url" @@ -84,6 +87,7 @@ class="bg-[#003285] opacity-80 hover:opacity-100 duration-200 text-white px-2 py <div class="flex flex-col px-4 py-2 gap-2"> @foreach ($konseps as $konsep) <div class="w-full"> + <h1>{{ $konsep->task->deskripsi }}</h1> <a href="{{ $konsep->berkas }}" class="w-full flex gap-2" target="_blank">{{ $konsep->name }} <svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" @@ -92,9 +96,9 @@ class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" d="M18 14v4.833A1.166 1.166 0 0 1 16.833 20H5.167A1.167 1.167 0 0 1 4 18.833V7.167A1.166 1.166 0 0 1 5.167 6h4.618m4.447-2H20v5.768m-7.889 2.121 7.778-7.778" /> </svg> </a> - <iframe> + {{-- <iframe> <embed class="w-full h-[60vh]" src="{{ $konsep->berkas }}" frameborder="0"></embed> - </iframe> + </iframe> --}} </div> @endforeach </div> @@ -103,6 +107,18 @@ class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" <script src="/script/nav.js"></script> <script src="/script/chatbot.js"></script> <script src="https://cdn.jsdelivr.net/npm/flowbite@2.5.2/dist/flowbite.min.js"></script> + <script> + document.querySelector("#task").addEventListener("change", function() { + var doc = this.options[this.selectedIndex].getAttribute("data-doc"); + if (doc) { + document.querySelector("#doc-url").classList.remove("hidden"); + document.querySelector("#doc-url").href = doc; + } else { + document.querySelector("#doc-url").classList.add("hidden"); + document.querySelector("#doc-url").href = ""; + } + }); + </script> <script> document .querySelector(`#url_set`) diff --git a/resources/views/rotasi/cabang/struktur-edit.blade.php b/resources/views/rotasi/cabang/struktur-edit.blade.php new file mode 100644 index 0000000..6b24742 --- /dev/null +++ b/resources/views/rotasi/cabang/struktur-edit.blade.php @@ -0,0 +1,121 @@ +<!DOCTYPE html> +<html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> + +<head> + @include('components/head') + <title>Air Mutasi | Profil</title> +</head> + +<body class="font-sans tracking-wider"> + @include('components/header', ['static' => true]) + @include('components.modal-component') + <main class="px-8 py-16"> + <div> + <select id="tambah-select-cabang"> + @foreach ($cabangs as $cabang) + <option value="{{ $cabang->id }}">{{ $cabang->nama }}</option> + @endforeach + </select> + <button onclick="tambah()">Tambah</button> + </div> + <div id="nodes"> + @foreach ($nodes as $node) + <div class="flex items-center justify-between" id="node-{{ $node->cabang_id }}"> + <p class="text-lg font-bold">{{ $node->cabang->nama }}</p> + <aside> + <select> + <option value="">PUSAT</option> + @foreach ($nodes as $snode) + <option value="{{ $snode->id }}" @if ($node->root_id == $snode->id) selected @endif> + {{ $snode->cabang->nama }} + </option> + @endforeach + </select> + <button onclick="edit({{ $node->cabang_id }})">Simpan</button> + <button onclick="hapus({{ $node->cabang_id }})">Hapus</button> + </aside> + </div> + @endforeach + </div> + </main> + <template id="node-template"> + <p class="text-lg font-bold" id="nama"></p> + <aside> + <select> + <option value="">PUSAT</option> + @foreach ($nodes as $snode) + <option value="{{ $snode->id }}"> + {{ $snode->cabang->nama }} + </option> + @endforeach + </select> + <button id="simpan">Simpan</button> + <button id="hapus">Hapus</button> + </aside> + </template> + @include('components.footer') + <script src="/script/nav.js"></script> + <script src="/script/chatbot.js"></script> + <script> + function edit(id) { + const cabang_id = id; + const root_id = document.getElementById(`node-${id}`).querySelector('select').value; + fetch("/api/struktur-cabang/edit", { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + cabang_id, + root_id: parseInt(root_id), + }) + }).then(response => response.json()) + .then(data => { + console.log(data); + alert('Berhasil disimpan'); + }) + .catch(error => console.error(error)); + } + + function hapus(id) { + const cabang_id = id; + fetch("/api/struktur-cabang/delete/" + cabang_id, { + method: 'DELETE', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + }).then(response => response.json()) + .then(data => { + console.log(data); + window.location.reload(); + }) + .catch(error => console.error(error)); + } + + function tambah() { + const nodes = document.getElementById('nodes'); + const selected_cabang = document.getElementById('tambah-select-cabang').value; + + const is_exists = document.getElementById(`node-${selected_cabang}`); + if (is_exists) { + alert('Cabang sudah ada'); + return; + } + + const nodeTemplate = document.getElementById('node-template').content.cloneNode(true); + nodeTemplate.querySelector('#nama').textContent = document.querySelector( + '#tambah-select-cabang > option:checked').textContent; + nodeTemplate.querySelector("#simpan").onclick = () => edit(selected_cabang); + nodeTemplate.querySelector("#hapus").onclick = () => hapus(selected_cabang); + + const item = document.createElement('div'); + item.classList.add('flex', 'items-center', 'justify-between'); + item.id = `node-${selected_cabang}`; + item.appendChild(nodeTemplate); + + nodes.appendChild(item); + } + </script> +</body> diff --git a/resources/views/rotasi/cabang/struktur.blade.php b/resources/views/rotasi/cabang/struktur.blade.php index 2aea137..2190317 100644 --- a/resources/views/rotasi/cabang/struktur.blade.php +++ b/resources/views/rotasi/cabang/struktur.blade.php @@ -9,12 +9,54 @@ <body class="font-sans tracking-wider"> @include('components/header', ['static' => true]) @include('components.modal-component') - <main class="px-8 py-16"> - <img src="/images/struktur.jpg" alt="struktur"> + <main class="px-8 py-16"> + <a href="/cabang/struktur/edit">Edit</a> + <div id="svg-tree"></div> </main> @include('components.footer') <script src="/script/nav.js"></script> <script src="/script/chatbot.js"></script> + <script src="https://cdn.jsdelivr.net/npm/apextree"></script> + <script> + const options = { + contentKey: 'data', + width: "100%", + height: "100vh", + nodeWidth: 250, + nodeHeight: 50, + fontColor: '#fff', + borderColor: '#333', + childrenSpacing: 50, + siblingSpacing: 20, + direction: 'left', + enableExpandCollapse: true, + nodeTemplate: (content) => + `<div class="text-black flex items-center justify-center h-full text-center">${content.name}</div>`, + canvasStyle: 'border: 1px solid black;background: #f6f6f6;', + enableToolbar: true, + }; + + fetch("/api/struktur-cabang", { + method: 'GET', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, + }).then(response => response.json()) + .then(data => { + data = { + id: "0", + data: { + name: 'MUTASI CABANG', + }, + children: data + }; + const treeContainer = document.getElementById('svg-tree'); + const tree = new ApexTree(treeContainer, options); + tree.render(data); + }) + .catch(error => console.error(error)); + </script> </body> </html> diff --git a/resources/views/welcome.blade.php b/resources/views/welcome.blade.php index 0852a1d..c8c8ccd 100644 --- a/resources/views/welcome.blade.php +++ b/resources/views/welcome.blade.php @@ -24,7 +24,7 @@ <div class="grid md:grid-cols-2 gap-4 mt-4"> <a href="/cabang/struktur" class="bg-[#003285] text-white p-4 rounded-md"> <p class="font-semibold text-2xl text-center">{{ $cabangs->count() }}+</p> - <p class="opacity-60 text-center">KANTOR CABANG</p> + <p class="opacity-60 text-center">STRUKTUR KANTOR CABANG</p> </a> <a href="/personel" class="bg-[#003285] text-white p-4 rounded-md"> <p class="font-semibold text-2xl text-center">{{ $personel }}+</p> diff --git a/routes/api.php b/routes/api.php index 580b883..dff9e37 100644 --- a/routes/api.php +++ b/routes/api.php @@ -36,6 +36,9 @@ Route::get('/{id}', [RotasiPengajuanController::class, 'byId']); }); +Route::get("/struktur-cabang", [RotasiCabangController::class, 'tree']); +Route::post("/struktur-cabang/edit", [RotasiCabangController::class, 'editNode']); +Route::delete("/struktur-cabang/delete/{cabang_id}", [RotasiCabangController::class, 'deleteNode']); Route::get("/personel", [PersonelController::class, 'search_by_nik']); Route::post('/upload-doc', [FileController::class, 'uploadDoc']); diff --git a/routes/web.php b/routes/web.php index 5038145..0d6099b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -38,6 +38,9 @@ Route::middleware("guest")->post("/login", [AuthController::class, 'login']); Route::middleware("auth:web")->get("/logout", [AuthController::class, 'logout'])->name('logout'); Route::middleware("auth:web")->get("/cabang/struktur", [RotasiCabangController::class, 'struktur'])->name('cabang.struktur'); +Route::group(['prefix' => "cabang/struktur/edit", 'middleware' => ["auth:web", "is.admin"]], function () { + Route::get("/", [RotasiCabangController::class, 'strukturEdit']); +}); Route::group(['prefix' => 'download', 'middleware' => ["auth:web"]], function () { Route::middleware('is.admin')->get('/pengajuan/{id}', [RotasiPengajuanController::class, 'document']);