create page tax with data, upload and export group by subdistrict

This commit is contained in:
arifal hidayat
2025-08-05 01:30:37 +07:00
parent 456eec83dc
commit 7135876ebc
18 changed files with 780 additions and 3 deletions

View File

@@ -0,0 +1,168 @@
import { Grid } from "gridjs/dist/gridjs.umd.js";
import "gridjs/dist/gridjs.umd.js";
import GlobalConfig from "../global-config";
import { addThousandSeparators } from "../global-config";
class Taxation {
constructor() {
this.toastMessage = document.getElementById("toast-message");
this.toastElement = document.getElementById("toastNotification");
this.toast = new bootstrap.Toast(this.toastElement);
this.table = null;
this.initTableTaxation();
// this.initEvents();
this.handleExportToExcel();
}
handleExportToExcel() {
const button = document.getElementById("btn-export-excel");
if (!button) {
console.error("Button not found: #btn-export-excel");
return;
}
const exportUrl = button.getAttribute("data-url");
button.addEventListener("click", function () {
button.disabled = true;
fetch(exportUrl, {
method: "GET",
credentials: "include",
headers: {
Authorization:
"Bearer " +
document
.querySelector('meta[name="api-token"]')
.getAttribute("content"),
},
})
.then(function (response) {
if (!response.ok) {
throw new Error(
"Error fetching data: " + response.statusText
);
}
return response.blob();
})
.then(function (blob) {
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "laporan-rekap-data-pembayaran.xlsx";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
})
.catch(function (error) {
console.error("Error fetching data:", error);
})
.finally(function () {
button.disabled = false;
});
});
}
initEvents() {
document.body.addEventListener("click", async (event) => {
const deleteButton = event.target.closest(".btn-delete-taxation");
if (deleteButton) {
event.preventDefault();
await this.handleDelete(deleteButton);
}
});
}
initTableTaxation() {
let tableContainer = document.getElementById("table-taxation");
if (!tableContainer) {
console.error("Table container not found!");
return;
}
// Clear previous table content
tableContainer.innerHTML = "";
this.table = new Grid({
columns: [
"ID",
{ name: "Tax No" },
{ name: "Tax Code" },
{ name: "WP Name" },
{ name: "Business Name" },
{ name: "Address" },
{ name: "Start Validity" },
{ name: "End Validity" },
{ name: "Tax Value" },
{ name: "Subdistrict" },
{ name: "Village" },
],
pagination: {
limit: 50,
server: {
url: (prev, page) => {
let separator = prev.includes("?") ? "&" : "?";
return `${prev}${separator}page=${page + 1}`;
},
},
},
sort: true,
search: {
server: {
url: (prev, keyword) => {
let separator = prev.includes("?") ? "&" : "?";
return `${prev}${separator}search=${encodeURIComponent(
keyword
)}`;
},
},
debounceTimeout: 1000,
},
server: {
url: `${GlobalConfig.apiHost}/api/taxs`,
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
"Content-Type": "application/json",
},
then: (data) => {
if (!data || !data.data) {
console.warn("⚠️ No data received from API");
return [];
}
return data.data.map((item) => {
return [
item.id,
item.tax_no,
item.tax_code,
item.wp_name,
item.business_name,
item.address,
item.start_validity,
item.end_validity,
addThousandSeparators(item.tax_value),
item.subdistrict,
item.village,
];
});
},
total: (data) => {
let totalRecords = data?.meta?.total || 0;
return totalRecords;
},
catch: (error) => {
console.error("❌ Error fetching data:", error);
},
},
}).render(tableContainer);
}
}
document.addEventListener("DOMContentLoaded", function (e) {
new Taxation();
});

View File

@@ -0,0 +1,79 @@
import { Dropzone } from "dropzone";
Dropzone.autoDiscover = false;
class UploadTaxation {
constructor() {
this.spatialDropzone = null;
this.formElement = document.getElementById("formUploadTaxation");
this.uploadButton = document.getElementById("submit-upload");
this.spinner = document.getElementById("spinner");
if (!this.formElement) {
console.error("Element formUploadTaxation tidak ditemukan!");
}
}
init() {
this.initDropzone();
this.setupUploadButton();
}
initDropzone() {
const toastNotification = document.getElementById("toastNotification");
const toast = new bootstrap.Toast(toastNotification);
let menuId = document.getElementById("menuId").value;
var previewTemplate,
dropzonePreviewNode = document.querySelector(
"#dropzone-preview-list"
);
(dropzonePreviewNode.id = ""),
dropzonePreviewNode &&
((previewTemplate = dropzonePreviewNode.parentNode.innerHTML),
dropzonePreviewNode.parentNode.removeChild(dropzonePreviewNode),
(this.spatialDropzone = new Dropzone(".dropzone", {
url: this.formElement.action,
method: "post",
acceptedFiles: ".xls,.xlsx",
previewTemplate: previewTemplate,
previewsContainer: "#dropzone-preview",
autoProcessQueue: false,
headers: {
Authorization: `Bearer ${document
.querySelector('meta[name="api-token"]')
.getAttribute("content")}`,
},
init: function () {
this.on("success", function (file, response) {
document.getElementById("toast-message").innerText =
response.message;
toast.show();
setTimeout(() => {
window.location.href = `/tax?menu_id=${menuId}`;
}, 2000);
});
this.on("error", function (file, errorMessage) {
document.getElementById("toast-message").innerText =
errorMessage.message;
toast.show();
this.uploadButton.disabled = false;
this.spinner.classList.add("d-none");
});
},
})));
}
setupUploadButton() {
this.uploadButton.addEventListener("click", (e) => {
if (this.spatialDropzone.files.length > 0) {
this.spatialDropzone.processQueue();
this.uploadButton.disabled = true;
this.spinner.classList.remove("d-none");
} else {
return;
}
});
}
}
document.addEventListener("DOMContentLoaded", function (e) {
new UploadTaxation().init();
});

View File

@@ -0,0 +1,38 @@
@extends('layouts.vertical', ['subtitle' => 'Pajak'])
@section('css')
@vite(['node_modules/gridjs/dist/theme/mermaid.min.css'])
@endsection
@section('content')
@include('layouts.partials/page-title', ['title' => 'Pajak', 'subtitle' => 'Data Pajak'])
<x-toast-notification />
<div class="row">
<div class="col-12">
<div class="card w-100">
<div class="card-header d-flex justify-content-end align-items-center">
<div class="d-flex gap-2">
<button class="btn btn-sm bg-black text-white d-flex align-items-center content-center gap-2" id="btn-export-excel" data-url="{{ route('api.taxs.export', ['menu_id' => $menuId]) }}">
<span>.xlsx</span>
<iconify-icon icon="mingcute:file-export-line" width="20" height="20" class="d-flex align-items-center"></iconify-icon>
</button>
</div>
</div>
<div class="card-body">
<div class="d-flex flex-wrap justify-content-end align-items-center mb-2">
<a href="{{ route('taxation.upload', ['menu_id' => $menuId]) }}" class="btn btn-primary btn-sm d-block d-sm-inline w-auto">Upload</a>
</div>
<div>
<div id="table-taxation"></div>
</div>
</div>
</div>
</div>
</div>
@endsection
@section('scripts')
@vite(['resources/js/taxation/index.js'])
@endsection

View File

@@ -0,0 +1,81 @@
@extends('layouts.vertical', ['subtitle' => 'Pajak'])
@section('content')
@include('layouts.partials/page-title', ['title' => 'Pajak', 'subtitle' => 'Upload'])
<x-toast-notification />
<input type="hidden" id="menuId" value="{{ $menuId ?? 0 }}">
<div class="row">
<div class="col-xl-12">
<div class="card">
<div class="card-header">
<h5 class="card-title">Upload Data Pajak</h5>
<p class="card-subtitle">
Please upload a file with the extension <strong>.xls or .xlsx</strong> with a maximum size of <strong>10 MB</strong>.
<br>
For <strong>.xls</strong> and <strong>.xlsx</strong> files, ensure that the data is contained within a <strong>single sheet</strong> with the following columns:
<strong>kode, no, npwpd, nama_wp, nama_usaha, alamat_usaha, masa_pajak, nilai_pajak, kecamatan, desa</strong>
</p>
</div>
<div class="card-body">
<div class="mb-3">
<div class="dropzone">
<form id="formUploadTaxation" action="{{ route('api.taxs.upload') }}" method="post" enctype="multipart/form-data">
<div class="fallback">
<!-- <input id="file-dropzone" type="file" name="file" accept=".xlsx,.xls" multiple/> -->
</div>
</form>
<div class="dz-message needsclick">
<i class="h1 bx bx-cloud-upload"></i>
<h3>Drop files here or click to upload.</h3>
</div>
</div>
<ul class="list-unstyled mb-0" id="dropzone-preview">
<li class="mt-2" id="dropzone-preview-list">
<!-- This is used as the file preview template -->
<div class="border rounded">
<div class="d-flex align-items-center p-2">
<div class="flex-shrink-0 me-3">
<div class="avatar-sm bg-light rounded">
<img data-dz-thumbnail class="img-fluid rounded d-block" src="#"
alt="" />
</div>
</div>
<div class="flex-grow-1">
<div class="pt-1">
<h5 class="fs-14 mb-1" data-dz-name>&nbsp;
</h5>
<p class="fs-13 text-muted mb-0" data-dz-size></p>
<strong class="error text-danger" data-dz-errormessage></strong>
</div>
</div>
<div class="flex-shrink-0 ms-3">
<button data-dz-remove class="btn btn-sm btn-danger">Delete</button>
</div>
</div>
</div>
</li>
</ul>
<!-- end dropzon-preview -->
</div>
<div class="d-flex justify-content-end">
<button id="submit-upload" class="btn btn-primary">
<span id="spinner" class="spinner-border spinner-border-sm me-1 d-none" role="status" aria-hidden="true"></span>
Upload Files
</button>
</div>
</div> <!-- end card body -->
</div> <!-- end card -->
</div> <!-- end col -->
</div> <!-- end row -->
@endsection
@section('scripts')
@vite(['resources/js/taxation/upload.js'])
@endsection