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']);