pesapakawan/wwwroot/website/js/datatables_pengolahan.js

524 lines
17 KiB
JavaScript

class CustomTable {
constructor(tableId, apiUrl) {
this.tableId = tableId;
this.apiUrl = apiUrl;
this.currentPage = 1;
this.pageSize = 10;
this.draw = 1;
this.searchTerm = "";
this.totalRecords = 0;
this.filteredRecords = 0;
this.data = [];
this.isLoading = false;
this.paginationClickTimeout = null;
this.init();
}
init() {
this.injectStyles();
this.createTableStructure();
this.createPaginationControls();
this.createSearchInput();
this.bindEvents();
this.loadData();
}
injectStyles() {
const styles = `
<style id="custom-table-styles">
/* Pagination touch handling untuk mobile */
#pagination {
touch-action: manipulation;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
#pagination button {
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
</style>
`;
const existingStyles = document.getElementById("custom-table-styles");
if (existingStyles) {
existingStyles.remove();
}
document.head.insertAdjacentHTML("beforeend", styles);
}
createTableStructure() {
const table = document.getElementById(this.tableId);
table.classList.add("modern-table");
let tbody = table.querySelector("tbody");
if (tbody) {
tbody.id = `${this.tableId}-tbody`;
}
this.showLoading();
}
createSearchInput() {}
createPaginationControls() {}
bindEvents() {
const searchInput = document.getElementById("searchInput");
const pageSizeSelect = document.getElementById("perPage");
if (searchInput) {
searchInput.addEventListener("keypress", (e) => {
if (e.key === "Enter") {
this.searchTerm = searchInput.value;
this.currentPage = 1;
this.draw++;
this.loadData();
}
});
searchInput.addEventListener("input", (e) => {
clearTimeout(this.searchTimeout);
this.searchTimeout = setTimeout(() => {
this.searchTerm = searchInput.value;
this.currentPage = 1;
this.draw++;
this.loadData();
}, 500);
});
}
if (pageSizeSelect) {
pageSizeSelect.addEventListener("change", (e) => {
this.pageSize = parseInt(e.target.value);
this.currentPage = 1;
this.draw++;
this.loadData();
});
}
}
async loadData() {
if (this.isLoading) {
console.log("Already loading, skipping...");
return;
}
console.log("Starting loadData...", {
currentPage: this.currentPage,
pageSize: this.pageSize,
searchTerm: this.searchTerm,
draw: this.draw,
});
this.isLoading = true;
this.showLoading();
try {
const start = (this.currentPage - 1) * this.pageSize;
const formData = new URLSearchParams();
formData.append("draw", this.draw.toString());
formData.append("start", start.toString());
formData.append("length", this.pageSize.toString());
if (this.searchTerm) {
formData.append("search[value]", this.searchTerm);
}
const response = await fetch(this.apiUrl, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: formData,
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
this.data = result.data || [];
this.totalRecords = result.recordsTotal || 0;
this.filteredRecords = result.recordsFiltered || 0;
console.log("Data loaded successfully:", {
dataLength: this.data.length,
totalRecords: this.totalRecords,
filteredRecords: this.filteredRecords,
});
this.renderTable();
this.renderPagination();
this.hideLoading();
} catch (error) {
console.error("Error loading data:", error);
this.showError("Gagal memuat data. Silakan coba lagi.");
} finally {
this.isLoading = false;
}
}
showLoading() {
const loadingIndicator = document.getElementById("loadingIndicator");
const emptyState = document.getElementById("emptyState");
const tableContainer = document.querySelector(".overflow-x-auto");
const pagination = document.getElementById("pagination");
if (loadingIndicator) loadingIndicator.classList.remove("hidden");
if (emptyState) emptyState.classList.add("hidden");
if (tableContainer) tableContainer.style.display = "none";
if (pagination) {
const buttons = pagination.querySelectorAll("button");
buttons.forEach((btn) => {
btn.style.opacity = "0.5";
btn.style.pointerEvents = "none";
});
}
}
showError(message) {
const loadingIndicator = document.getElementById("loadingIndicator");
const emptyState = document.getElementById("emptyState");
const tableContainer = document.querySelector(".overflow-x-auto");
if (loadingIndicator) {
loadingIndicator.classList.add("hidden");
}
if (emptyState) {
emptyState.classList.remove("hidden");
emptyState.innerHTML = `
<div class="text-center py-8">
<div class="text-red-400 mb-4">
<i class="fas fa-exclamation-triangle text-4xl"></i>
</div>
<p class="text-sm text-red-600">${message}</p>
</div>
`;
}
if (tableContainer) tableContainer.style.display = "none";
}
hideLoading() {
const loadingIndicator = document.getElementById("loadingIndicator");
const emptyState = document.getElementById("emptyState");
const tableContainer = document.querySelector(".overflow-x-auto");
const pagination = document.getElementById("pagination");
if (loadingIndicator) loadingIndicator.classList.add("hidden");
if (emptyState) emptyState.classList.add("hidden");
if (tableContainer) tableContainer.style.display = "block";
if (pagination) {
const buttons = pagination.querySelectorAll("button");
buttons.forEach((btn) => {
if (!btn.hasAttribute("disabled")) {
btn.style.opacity = "1";
btn.style.pointerEvents = "auto";
}
});
}
}
renderTable() {
const tbody =
document.querySelector(`#${this.tableId} tbody`) ||
document.getElementById(`${this.tableId}-tbody`);
const emptyState = document.getElementById("emptyState");
const tableContainer = document.querySelector(".overflow-x-auto");
if (this.data.length === 0) {
if (tableContainer) tableContainer.style.display = "none";
if (emptyState) emptyState.classList.remove("hidden");
this.updateTableInfo();
this.renderPagination();
return;
}
if (tableContainer) tableContainer.style.display = "block";
if (emptyState) emptyState.classList.add("hidden");
const start = (this.currentPage - 1) * this.pageSize;
let html = "";
this.data.forEach((row, index) => {
const no = start + index + 1;
const dateInfo = this.formatDate(row.tglBerlakuIzin);
html += `
<tr class="hover:bg-cyan-50 transition-all duration-200">
<td class="px-6 py-4 text-sm text-gray-900 text-center font-medium">${no}</td>
<td class="px-6 py-4 text-sm text-gray-900 font-medium">
<div class="font-medium text-gray-900">${this.escapeHtml(
row.nama || "-"
)}</div>
</td>
<td class="px-6 py-4 text-sm text-gray-700">
<div class="text-gray-700">${this.escapeHtml(
row.alamat || "-"
)}</div>
</td>
<td class="px-6 py-4 text-sm text-gray-900 text-center">
<div class="text-center">
<div class="text-sm font-medium text-gray-900 mb-1">${
dateInfo.formatted
}</div>
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${
dateInfo.statusClass
}">
${dateInfo.statusText}
</span>
</div>
</td>
</tr>
`;
});
tbody.innerHTML = html;
this.updateTableInfo();
}
renderPagination() {
const container = document.getElementById("pagination");
const totalPages = Math.ceil(this.filteredRecords / this.pageSize);
if (!container) return;
let paginationHtml = "";
paginationHtml += `
<button ${this.currentPage === 1 ? 'disabled="disabled"' : ""}
data-page="${this.currentPage - 1}"
style="touch-action: manipulation;"
class="px-3 py-2 text-sm border border-gray-300 rounded-md bg-white text-gray-700 hover:bg-cyan-50 hover:text-cyan-600 hover:border-cyan-300 transition-all duration-200 ${
this.currentPage === 1
? "opacity-50 cursor-not-allowed"
: ""
}">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
</svg>
</button>
`;
const startPage = Math.max(1, this.currentPage - 2);
const endPage = Math.min(totalPages, this.currentPage + 2);
if (startPage > 1) {
paginationHtml += `<button data-page="1" style="touch-action: manipulation;" class="px-3 py-2 mx-1 text-sm border border-gray-300 rounded-md bg-white text-gray-700 hover:bg-cyan-50 hover:text-cyan-600 hover:border-cyan-300 transition-all duration-200">1</button>`;
if (startPage > 2) {
paginationHtml += `<span class="px-3 py-2 text-sm text-gray-500">...</span>`;
}
}
for (let i = startPage; i <= endPage; i++) {
paginationHtml += `
<button data-page="${i}"
style="touch-action: manipulation;"
class="px-3 py-2 mx-1 text-sm border rounded-md transition-all duration-200 ${
i === this.currentPage
? "bg-cyan-600 text-white border-cyan-600 hover:bg-cyan-700"
: "border-gray-300 bg-white text-gray-700 hover:bg-cyan-50 hover:text-cyan-600 hover:border-cyan-300"
}">
${i}
</button>
`;
}
if (endPage < totalPages) {
if (endPage < totalPages - 1) {
paginationHtml += `<span class="px-3 py-2 text-sm text-gray-500">...</span>`;
}
paginationHtml += `<button data-page="${totalPages}" style="touch-action: manipulation;" class="px-3 py-2 mx-1 text-sm border border-gray-300 rounded-md bg-white text-gray-700 hover:bg-cyan-50 hover:text-cyan-600 hover:border-cyan-300 transition-all duration-200">${totalPages}</button>`;
}
paginationHtml += `
<button ${
this.currentPage === totalPages ? 'disabled="disabled"' : ""
}
data-page="${this.currentPage + 1}"
style="touch-action: manipulation;"
class="px-3 py-2 text-sm border border-gray-300 rounded-md bg-white text-gray-700 hover:bg-cyan-50 hover:text-cyan-600 hover:border-cyan-300 transition-all duration-200 ${
this.currentPage === totalPages
? "opacity-50 cursor-not-allowed"
: ""
}">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
</svg>
</button>
`;
container.innerHTML = paginationHtml;
if (container._clickHandler) {
container.removeEventListener("click", container._clickHandler);
}
if (container._touchHandler) {
container.removeEventListener("touchstart", container._touchHandler);
}
if (container._touchEndHandler) {
container.removeEventListener("touchend", container._touchEndHandler);
}
container._clickHandler = (e) => {
e.preventDefault();
e.stopPropagation();
console.log("Pagination clicked:", e.target);
let clickedButton = e.target;
if (e.target.tagName === "SVG" || e.target.tagName === "PATH") {
clickedButton = e.target.closest("button");
}
if (
clickedButton &&
clickedButton.tagName === "BUTTON" &&
clickedButton.dataset.page
) {
console.log("Button clicked:", {
page: clickedButton.dataset.page,
disabled: clickedButton.disabled,
hasAttribute: clickedButton.hasAttribute("disabled"),
});
const page = parseInt(clickedButton.dataset.page);
if (
page &&
page !== this.currentPage &&
page >= 1 &&
page <= totalPages &&
!clickedButton.disabled &&
!clickedButton.hasAttribute("disabled") &&
!this.isLoading
) {
console.log("Navigating to page:", page);
if (this.paginationClickTimeout) {
clearTimeout(this.paginationClickTimeout);
}
this.paginationClickTimeout = setTimeout(() => {
this.currentPage = page;
this.draw++;
this.loadData();
}, 100);
} else {
console.log("Click ignored:", {
samePage: page === this.currentPage,
outOfRange: page < 1 || page > totalPages,
disabled:
clickedButton.disabled || clickedButton.hasAttribute("disabled"),
loading: this.isLoading,
});
}
}
};
container._touchHandler = (e) => {
e.preventDefault();
e.stopPropagation();
};
container.addEventListener("click", container._clickHandler);
container.addEventListener("touchstart", container._touchHandler, {
passive: false,
});
container._touchEndHandler = (e) => {
e.preventDefault();
e.stopPropagation();
};
container.addEventListener("touchend", container._touchEndHandler, {
passive: false,
});
}
updateTableInfo() {
const infoContainer = document.getElementById("tableInfo");
if (infoContainer) {
const start = (this.currentPage - 1) * this.pageSize + 1;
const end = Math.min(
this.currentPage * this.pageSize,
this.filteredRecords
);
infoContainer.innerHTML = `Menampilkan ${start} hingga ${end} dari ${this.filteredRecords} data`;
}
}
formatDate(dateString) {
if (!dateString || dateString === "0001-01-01") {
return {
formatted: "-",
statusClass: "bg-gray-100 text-gray-800",
statusText: "Tidak diketahui",
};
}
try {
const date = new Date(dateString);
const formattedDate = date.toLocaleDateString("id-ID", {
year: "numeric",
month: "2-digit",
day: "2-digit",
});
const today = new Date();
const daysUntilExpiry = Math.ceil((date - today) / (1000 * 60 * 60 * 24));
let statusClass = "bg-green-100 text-green-800";
let statusText = `${daysUntilExpiry} hari`;
if (daysUntilExpiry < 0) {
statusClass = "bg-red-100 text-red-800";
statusText = "Expired";
} else if (daysUntilExpiry < 30) {
statusClass = "bg-red-100 text-red-800";
} else if (daysUntilExpiry < 90) {
statusClass = "bg-yellow-100 text-yellow-800";
}
return {
formatted: formattedDate,
statusClass: statusClass,
statusText: statusText,
};
} catch (error) {
return {
formatted: dateString,
statusClass: "bg-gray-100 text-gray-800",
statusText: "Invalid",
};
}
}
escapeHtml(text) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
}
document.addEventListener("DOMContentLoaded", function () {
const tableElement = document.getElementById("pengolahanTable");
if (tableElement && !tableElement.customTableInitialized) {
tableElement.customTableInitialized = true;
console.log("Initializing CustomTable...");
new CustomTable(
"pengolahanTable",
"https://pesapakawan.dinaslhdki.id/api/web/jasa/olah"
);
}
});