diff --git a/app/Http/Controllers/Api/UsersController.php b/app/Http/Controllers/Api/UsersController.php index abffb5a..1ea9d9b 100644 --- a/app/Http/Controllers/Api/UsersController.php +++ b/app/Http/Controllers/Api/UsersController.php @@ -85,4 +85,17 @@ class UsersController extends Controller return response()->json(['message' => $e->getMessage()],500); } } + + public function destroy($id){ + try{ + $user = User::findOrFail($id); + DB::beginTransaction(); + $user->delete(); + DB::commit(); + return response()->json(['message' => 'Successfully deleted'], 200); + }catch(\Exception $e){ + Log::error('Failed to delete user: '. $e->getMessage()); + return response()->json(['message' => 'Failed to delete user'],500); + } + } } diff --git a/app/Models/User.php b/app/Models/User.php index 997e698..2b5c2a2 100755 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -4,6 +4,7 @@ namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\HasApiTokens; @@ -11,7 +12,7 @@ use Laravel\Sanctum\HasApiTokens; class User extends Authenticatable { /** @use HasFactory<\Database\Factories\UserFactory> */ - use HasFactory, Notifiable, HasApiTokens; + use HasFactory, Notifiable, HasApiTokens, SoftDeletes; /** * The attributes that are mass assignable. @@ -27,6 +28,8 @@ class User extends Authenticatable 'position' ]; + protected $dates = ['deleted_at']; + /** * The attributes that should be hidden for serialization. * @@ -50,6 +53,12 @@ class User extends Authenticatable ]; } + public function delete(){ + $this->email = $this->email . '_deleted_'. now()->timestamp; + $this->save(); + return parent::delete(); + } + public function roles(){ return $this->belongsToMany(Role::class, 'user_role')->withTimestamps(); } diff --git a/database/migrations/2025_03_19_133310_add_soft_deletes_to_users_table.php b/database/migrations/2025_03_19_133310_add_soft_deletes_to_users_table.php new file mode 100644 index 0000000..a7bda58 --- /dev/null +++ b/database/migrations/2025_03_19_133310_add_soft_deletes_to_users_table.php @@ -0,0 +1,28 @@ +softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropSoftDeletes(); + }); + } +}; diff --git a/resources/js/dashboards/pbg.js b/resources/js/dashboards/pbg.js index 1c128a3..dbc0620 100644 --- a/resources/js/dashboards/pbg.js +++ b/resources/js/dashboards/pbg.js @@ -3,107 +3,99 @@ import GlobalConfig, { addThousandSeparators } from "../global-config.js"; import ApexCharts from "apexcharts"; import "gridjs/dist/gridjs.umd.js"; import GeneralTable from "../table-generator.js"; +import InitDatePicker from "../utils/InitDatePicker.js"; var chart; -document.addEventListener("DOMContentLoaded", async function () { - await initChart(); - const yearPicker = document.getElementById("yearPicker"); - async function updateDataByYear(selectedYear) { - // Target PAD Element +class DashboardPBG { + async init() { + try { + new InitDatePicker( + "#datepicker-dashboard-pbg", + this.handleChangedDate.bind(this) + ).init(); + + // Load initial data + this.updateData("latest"); + } catch (error) { + console.error("Error initializing data:", error); + } + } + + handleChangedDate(filterDate) { + if (!filterDate) return; + this.updateData(filterDate); + } + + async updateData(filterDate) { + let resumeData = await this.getResume(filterDate); + if (!resumeData) return; + + let targetPAD = resumeData.target_pad.sum; const targetPadElement = document.getElementById("target-pad"); - if (!targetPadElement) return; - const targetPadValue = await getDataSettings("TARGET_PAD"); - targetPadElement.textContent = formatCurrency(targetPadValue); + targetPadElement.textContent = formatCurrency(targetPAD); - // Total Potensi Berkas const totalPotensiBerkas = document.getElementById( "total-potensi-berkas" ); - if (!totalPotensiBerkas) return; - const totalPotensiBerkasValue = await getDataTotalPotensi(selectedYear); totalPotensiBerkas.textContent = formatCurrency( - totalPotensiBerkasValue.totalData + resumeData.total_potensi.sum ); - // Total Berkas Terverifikasi const totalBerkasTerverifikasi = document.getElementById( "total-berkas-terverifikasi" ); - if (!totalBerkasTerverifikasi) return; - const totalBerkasTerverifikasiValue = await getDataVerification( - selectedYear - ); totalBerkasTerverifikasi.textContent = formatCurrency( - totalBerkasTerverifikasiValue.totalData + resumeData.verified_document.sum ); - // Total Kekurangan potensi const totalKekuranganPotensi = document.getElementById( "total-kekurangan-potensi" ); - if (!totalKekuranganPotensi) return; - const totalKekuranganPotensiValue = - new Big(targetPadValue) - - new Big(totalPotensiBerkasValue.totalData); totalKekuranganPotensi.textContent = formatCurrency( - totalKekuranganPotensiValue + resumeData.kekurangan_potensi.sum ); - // Total Potensi PBG dari tata ruang const totalPotensiPBGTataRuang = document.getElementById( "total-potensi-pbd-tata-ruang" ); - if (!totalPotensiPBGTataRuang) return; - const totalPotensiPBGTataRuangValue = await getDataSettings( - "TATA_RUANG" - ); - totalPotensiPBGTataRuang.textContent = formatCurrency( - totalPotensiPBGTataRuangValue - ); + totalPotensiPBGTataRuang.textContent = "Rp.-"; - // Total Berkas Belum terverifikasi const totalBerkasBelumTerverifikasi = document.getElementById( "total-berkas-belum-terverifikasi" ); - if (!totalBerkasBelumTerverifikasi) return; - const totalBerkasBelumTerverifikasiValue = await getDataNonVerification( - selectedYear - ); - const totalBerkasBelumTerverifikasiCount = - totalBerkasBelumTerverifikasiValue.countData; totalBerkasBelumTerverifikasi.textContent = formatCurrency( - totalBerkasBelumTerverifikasiValue.totalData + resumeData.non_verified_document.sum ); + const totalRealisasiTerbitPBG = document.getElementById( + "realisasi-terbit-pbg" + ); + totalRealisasiTerbitPBG.textContent = formatCurrency( + resumeData.realisasi_terbit.sum + ); + + const totalProsesDinasTeknis = document.getElementById( + "processing-technical-services" + ); + totalProsesDinasTeknis.textContent = formatCurrency( + resumeData.proses_dinas_teknis.sum + ); + + await this.initPieChart(resumeData); + } + + async initPieChart(data) { // Total Berkas Usaha - const totalBerkasUsahaValue = await getDataBusiness(selectedYear); - const totalBerkasUsahaCount = totalBerkasUsahaValue.countData; - const totalBerkasUsahaTotalData = totalBerkasUsahaValue.totalData; + const totalBerkasUsahaTotalData = data.verified_document.sum; // Total Berkas Non Usaha - const totalBerkasNonUsahaValue = await getDataNonBusiness(selectedYear); - const totalBerkasNonUsahaCount = totalBerkasNonUsahaValue.countData; - const totalBerkasNonUsahaTotalData = totalBerkasNonUsahaValue.totalData; + const totalBerkasNonUsahaTotalData = data.non_verified_document.sum; // Pie Chart Section - let persenUsaha = - totalBerkasBelumTerverifikasiCount > 0 - ? ( - (totalBerkasUsahaCount / - totalBerkasBelumTerverifikasiCount) * - 100 - ).toFixed(2) - : "0"; + let persenUsaha = data.verified_document.percentage; - let persenNonUsaha = - totalBerkasBelumTerverifikasiCount > 0 - ? ( - (totalBerkasNonUsahaCount / - totalBerkasBelumTerverifikasiCount) * - 100 - ).toFixed(2) - : "0"; + let persenNonUsaha = data.non_verified_document.percentage; const dataSeriesPieChart = [ Number(persenUsaha), @@ -123,7 +115,41 @@ document.addEventListener("DOMContentLoaded", async function () { ).textContent = persenUsaha + "%"; updatePieChart(dataSeriesPieChart, labelsPieChart); + } + async getResume(filterByDate) { + try { + const response = await fetch( + `${GlobalConfig.apiHost}/api/bigdata-resume?filterByDate=${filterByDate}`, + { + credentials: "include", + headers: { + Authorization: `Bearer ${ + document.querySelector("meta[name='api-token']") + .content + }`, + "Content-Type": "application/json", + }, + } + ); + + if (!response.ok) { + console.error("Network response was not ok", response); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error("Error fetching chart data:", error); + return null; + } + } +} +document.addEventListener("DOMContentLoaded", async function (e) { + await new DashboardPBG().init(); + await initChart(); + + async function updateDataByYear() { // Load all Tourism location const allLocation = await getAllLocation(); console.log(allLocation); @@ -159,42 +185,6 @@ document.addEventListener("DOMContentLoaded", async function () { .bindTooltip(loc.name, { permanent: false, direction: "top" }); // Tooltip saat di-hover }); - // Realisasi terbit PBG - const totalRealisasiTerbitPBG = document.getElementById( - "realisasi-terbit-pbg" - ); - if (!totalRealisasiTerbitPBG) return; - const totalRealisasiTerbitPBGValue = await getDataSettings( - "REALISASI_TERBIT_PBG_SUM" - ); - totalRealisasiTerbitPBG.textContent = formatCurrency( - totalRealisasiTerbitPBGValue - ); - - // Menunggu Klik DPMPTSP - const totalMenungguKlikDpmptsp = document.getElementById( - "waiting-click-dpmptsp" - ); - if (!totalMenungguKlikDpmptsp) return; - const totalMenungguKlikDpmptspValue = await getDataSettings( - "MENUNGGU_KLIK_DPMPTSP_SUM" - ); - totalMenungguKlikDpmptsp.textContent = formatCurrency( - totalMenungguKlikDpmptspValue - ); - - // Proses Dinas Teknis - const totalProsesDinasTeknis = document.getElementById( - "processing-technical-services" - ); - if (!totalProsesDinasTeknis) return; - const totalProsesDinasTeknisValue = await getDataSettings( - "PROSES_DINAS_TEKNIS_SUM" - ); - totalProsesDinasTeknis.textContent = formatCurrency( - totalProsesDinasTeknisValue - ); - // Load Tabel Baru di Update const tableLastUpdated = new GeneralTable( "pbg-filter-by-updated-at", @@ -255,188 +245,9 @@ document.addEventListener("DOMContentLoaded", async function () { ).hidden = true; } - await updateDataByYear(yearPicker.value); - - yearPicker.addEventListener("change", async function () { - console.log("event change dropdown"); - await updateDataByYear(yearPicker.value); - }); + await updateDataByYear(); }); -async function getDataSettings(string_key) { - try { - const response = await fetch( - `${GlobalConfig.apiHost}/api/data-settings?search=${string_key}`, - { - credentials: "include", - headers: { - Authorization: `Bearer ${ - document.querySelector("meta[name='api-token']").content - }`, - "Content-Type": "application/json", - }, - } - ); - - if (!response.ok) { - console.error("Network response was not ok", response); - } - - const data = await response.json(); - return data.data?.[0]?.value ?? 0; // Pastikan tidak error jika data kosong - } catch (error) { - console.error("Error fetching data:", error); - return 0; - } -} - -async function getDataTotalPotensi(year) { - try { - const response = await fetch( - `${GlobalConfig.apiHost}/api/all-task-documents?year=${year}`, - { - credentials: "include", - headers: { - Authorization: `Bearer ${ - document.querySelector("meta[name='api-token']").content - }`, - "Content-Type": "application/json", - }, - } - ); - - if (!response.ok) { - console.error("Network response was not ok", response); - } - - const data = await response.json(); - return { - totalData: data.data.total, - }; - } catch (error) { - console.error("Error fetching chart data:", error); - return null; - } -} - -async function getDataVerification(year) { - try { - const response = await fetch( - `${GlobalConfig.apiHost}/api/verification-documents?year=${year}`, - { - credentials: "include", - headers: { - Authorization: `Bearer ${ - document.querySelector("meta[name='api-token']").content - }`, - "Content-Type": "application/json", - }, - } - ); - - if (!response.ok) { - console.error("Network response was not ok", response); - } - - const data = await response.json(); - return { - totalData: data.data.total, - }; - } catch (error) { - console.error("Error fetching chart data:", error); - return 0; - } -} - -async function getDataNonVerification(year) { - try { - const response = await fetch( - `${GlobalConfig.apiHost}/api/non-verification-documents?year=${year}`, - { - credentials: "include", - headers: { - Authorization: `Bearer ${ - document.querySelector("meta[name='api-token']").content - }`, - "Content-Type": "application/json", - }, - } - ); - - if (!response.ok) { - console.error("Network response was not ok", response); - } - - const data = await response.json(); - return { - countData: data.data.count, - totalData: data.data.total, - }; - } catch (error) { - console.error("Error fetching chart data:", error); - return 0; - } -} - -async function getDataBusiness(year) { - try { - const response = await fetch( - `${GlobalConfig.apiHost}/api/business-documents?year=${year}`, - { - credentials: "include", - headers: { - Authorization: `Bearer ${ - document.querySelector("meta[name='api-token']").content - }`, - "Content-Type": "application/json", - }, - } - ); - - if (!response.ok) { - console.error("Network response was not ok", response); - } - - const data = await response.json(); - return { - countData: data.data.count, - totalData: data.data.total, - }; - } catch (error) { - console.error("Error fetching chart data:", error); - return 0; - } -} - -async function getDataNonBusiness(year) { - try { - const response = await fetch( - `${GlobalConfig.apiHost}/api/non-business-documents?year=${year}`, - { - credentials: "include", - headers: { - Authorization: `Bearer ${ - document.querySelector("meta[name='api-token']").content - }`, - "Content-Type": "application/json", - }, - } - ); - - if (!response.ok) { - console.error("Network response was not ok", response); - } - const data = await response.json(); - return { - countData: data.data.count, - totalData: data.data.total, - }; - } catch (error) { - console.error("Error fetching chart data:", error); - return 0; - } -} - async function getAllLocation() { try { const response = await fetch( diff --git a/resources/js/master/users/users.js b/resources/js/master/users/users.js index a9f74d2..8cdde50 100644 --- a/resources/js/master/users/users.js +++ b/resources/js/master/users/users.js @@ -2,10 +2,25 @@ import { Grid } from "gridjs/dist/gridjs.umd.js"; import gridjs from "gridjs/dist/gridjs.umd.js"; import "gridjs/dist/gridjs.umd.js"; import GlobalConfig from "../../global-config"; +import Swal from "sweetalert2"; class UsersTable { - init() { + constructor() { + this.toastMessage = document.getElementById("toast-message"); + this.toastElement = document.getElementById("toastNotification"); + this.toast = new bootstrap.Toast(this.toastElement); + this.table = null; this.initTableUsers(); + this.initEvents(); + } + initEvents() { + document.body.addEventListener("click", async (event) => { + const deleteButton = event.target.closest(".btn-delete-users"); + if (deleteButton) { + event.preventDefault(); + await this.handleDelete(deleteButton); + } + }); } initTableUsers() { @@ -14,7 +29,8 @@ class UsersTable { tableContainer.innerHTML = ""; let canUpdate = tableContainer.getAttribute("data-updater") === "1"; - new Grid({ + let canDestroy = tableContainer.getAttribute("data-destroyer") === "1"; + this.table = new Grid({ columns: [ "ID", "Name", @@ -26,18 +42,28 @@ class UsersTable { { name: "Action", formatter: (cell) => { - if (!canUpdate) { + if (!canUpdate && !canDestroy) { return gridjs.html( `No Privilege` ); } - return gridjs.html(` -
`; + + return gridjs.html(buttons); }, }, ], @@ -67,7 +93,6 @@ class UsersTable { "Content-Type": "application/json", }, then: (data) => { - console.log(data.data); return data.data.map((item) => [ item.id, item.name, @@ -83,8 +108,62 @@ class UsersTable { }, }).render(document.getElementById("table-users")); } + + async handleDelete(button) { + const id = button.getAttribute("data-id"); + + const result = await Swal.fire({ + title: "Are you sure?", + text: "You won't be able to revert this!", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + confirmButtonText: "Yes, delete it!", + }); + + if (result.isConfirmed) { + try { + let response = await fetch( + `${GlobalConfig.apiHost}/api/users/${id}`, + { + method: "DELETE", + credentials: "include", + headers: { + Authorization: `Bearer ${document + .querySelector('meta[name="api-token"]') + .getAttribute("content")}`, + "Content-Type": "application/json", + }, + } + ); + + if (response.ok) { + let result = await response.json(); + this.toastMessage.innerText = + result.message || "Deleted successfully!"; + this.toast.show(); + + // Refresh Grid.js table + if (typeof this.table !== "undefined") { + this.table.updateConfig({}).forceRender(); + } + } else { + let error = await response.json(); + console.error("Delete failed:", error); + this.toastMessage.innerText = + error.message || "Delete failed!"; + this.toast.show(); + } + } catch (error) { + console.error("Error deleting item:", error); + this.toastMessage.innerText = "An error occurred!"; + this.toast.show(); + } + } + } } document.addEventListener("DOMContentLoaded", function (e) { - new UsersTable().init(); + new UsersTable(); }); diff --git a/resources/js/roles/index.js b/resources/js/roles/index.js index 1b970c1..5347e04 100644 --- a/resources/js/roles/index.js +++ b/resources/js/roles/index.js @@ -56,7 +56,7 @@ class Roles { if (canDelete) { buttons += ` -