update: datatables pengolahan
parent
413cd6017a
commit
52094bcb07
|
|
@ -285,35 +285,78 @@
|
|||
</div>
|
||||
|
||||
<div class="my-12 max-w-6xl mx-auto px-4">
|
||||
<div class="bg-white rounded-xl shadow-lg p-6 border border-gray-200">
|
||||
<div class="mb-6">
|
||||
<h2 class="text-2xl font-bold text-gray-800 mb-2">Daftar Jasa Pengolahan Sampah</h2>
|
||||
<p class="text-gray-600">Data perusahaan yang memiliki izin pengolahan sampah di DKI Jakarta</p>
|
||||
<div class="bg-gradient-to-br from-white to-gray-50 rounded-2xl shadow-xl border border-gray-100 overflow-hidden">
|
||||
<div class="bg-gradient-to-r from-cyan-600 to-blue-600 p-6">
|
||||
<h2 class="text-2xl font-bold text-white mb-2">Daftar Jasa Pengolahan Sampah</h2>
|
||||
<p class="text-cyan-100">Data perusahaan yang memiliki izin pengolahan sampah di DKI Jakarta</p>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table id="pengolahanTable" class="w-full bg-white table-fixed" style="width:100%">
|
||||
<thead class="bg-cyan-400 text-white">
|
||||
<tr>
|
||||
<th class="px-3 py-4 text-left text-white text-sm font-semibold uppercase tracking-wider" style="width: 5%">No</th>
|
||||
<th class="px-3 py-4 text-left text-white text-sm font-semibold uppercase tracking-wider" style="width: 25%">Perusahaan</th>
|
||||
<th class="px-3 py-4 text-left text-white text-sm font-semibold uppercase tracking-wider" style="width: 50%">Alamat</th>
|
||||
<th class="px-3 py-4 text-left text-white text-sm font-semibold uppercase tracking-wider" style="width: 20%">Berlaku Hingga</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
<div class="p-6">
|
||||
<div class="mb-6 flex flex-col sm:flex-row justify-between items-center space-y-4 sm:space-y-0 sm:space-x-4">
|
||||
<div class="flex items-center space-x-2">
|
||||
<label class="text-sm text-gray-600 font-medium">Tampilkan:</label>
|
||||
<select id="perPage" class="px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-cyan-500 focus:border-transparent text-sm">
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
<span class="text-sm text-gray-600">data per halaman</span>
|
||||
</div>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input type="text" id="searchInput" placeholder="Cari perusahaan atau alamat..."
|
||||
class="pl-10 pr-4 py-2 w-80 border border-gray-300 rounded-lg focus:ring-2 focus:ring-cyan-500 focus:border-transparent text-sm transition-all duration-200">
|
||||
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
||||
<svg class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 text-sm text-gray-500 text-center" id="loadingIndicator">
|
||||
<div class="inline-flex items-center">
|
||||
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
Memuat data...
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200 overflow-hidden">
|
||||
<div class="overflow-x-auto">
|
||||
<table id="pengolahanTable" class="w-full">
|
||||
<thead class="bg-gradient-to-r from-cyan-500 to-blue-500">
|
||||
<tr>
|
||||
<th class="px-6 py-4 text-left text-white text-sm font-semibold uppercase tracking-wider w-16">No</th>
|
||||
<th class="px-6 py-4 text-left text-white text-sm font-semibold uppercase tracking-wider">Perusahaan</th>
|
||||
<th class="px-6 py-4 text-left text-white text-sm font-semibold uppercase tracking-wider">Alamat</th>
|
||||
<th class="px-6 py-4 text-center text-white text-sm font-semibold uppercase tracking-wider w-48">Berlaku Hingga</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tableBody" class="bg-white divide-y divide-gray-200">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="loadingIndicator" class="text-center py-8">
|
||||
<div class="inline-flex items-center text-cyan-600">
|
||||
<svg class="animate-spin -ml-1 mr-3 h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
<span class="text-sm font-medium">Memuat data...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="emptyState" class="hidden text-center py-8">
|
||||
<div class="text-gray-400 mb-4">
|
||||
<svg class="mx-auto h-12 w-12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500">Tidak ada data yang ditemukan</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex flex-col sm:flex-row justify-between items-center space-y-4 sm:space-y-0">
|
||||
<div id="tableInfo" class="text-sm text-gray-600">
|
||||
</div>
|
||||
|
||||
<nav id="pagination" class="flex space-x-1">
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -85,17 +85,14 @@
|
|||
--color-indigo-50: oklch(96.2% 0.018 272.314);
|
||||
--color-indigo-100: oklch(93% 0.034 272.788);
|
||||
--color-indigo-300: oklch(78.5% 0.115 274.713);
|
||||
--color-indigo-800: oklch(39.8% 0.195 277.366);
|
||||
--color-purple-50: oklch(97.7% 0.014 308.299);
|
||||
--color-purple-100: oklch(94.6% 0.033 307.174);
|
||||
--color-purple-200: oklch(90.2% 0.063 306.703);
|
||||
--color-purple-500: oklch(62.7% 0.265 303.9);
|
||||
--color-purple-600: oklch(55.8% 0.288 302.321);
|
||||
--color-purple-700: oklch(49.6% 0.265 301.924);
|
||||
--color-purple-800: oklch(43.8% 0.218 303.724);
|
||||
--color-purple-900: oklch(38.1% 0.176 304.987);
|
||||
--color-pink-50: oklch(97.1% 0.014 343.198);
|
||||
--color-pink-500: oklch(65.6% 0.241 354.308);
|
||||
--color-rose-50: oklch(96.9% 0.015 12.422);
|
||||
--color-rose-500: oklch(64.5% 0.246 16.439);
|
||||
--color-rose-600: oklch(58.6% 0.253 17.585);
|
||||
|
|
@ -117,9 +114,6 @@
|
|||
--color-gray-700: oklch(37.3% 0.034 259.733);
|
||||
--color-gray-800: oklch(27.8% 0.033 256.848);
|
||||
--color-gray-900: oklch(21% 0.034 264.665);
|
||||
--color-neutral-200: oklch(92.2% 0 0);
|
||||
--color-neutral-600: oklch(43.9% 0 0);
|
||||
--color-neutral-800: oklch(26.9% 0 0);
|
||||
--color-black: #000;
|
||||
--color-white: #fff;
|
||||
--spacing: 0.25rem;
|
||||
|
|
@ -408,9 +402,6 @@
|
|||
.top-0 {
|
||||
top: calc(var(--spacing) * 0);
|
||||
}
|
||||
.top-1 {
|
||||
top: calc(var(--spacing) * 1);
|
||||
}
|
||||
.top-1\/2 {
|
||||
top: calc(1/2 * 100%);
|
||||
}
|
||||
|
|
@ -465,9 +456,6 @@
|
|||
.right-full {
|
||||
right: 100%;
|
||||
}
|
||||
.-bottom-0 {
|
||||
bottom: calc(var(--spacing) * -0);
|
||||
}
|
||||
.-bottom-0\.5 {
|
||||
bottom: calc(var(--spacing) * -0.5);
|
||||
}
|
||||
|
|
@ -504,9 +492,6 @@
|
|||
.left-0 {
|
||||
left: calc(var(--spacing) * 0);
|
||||
}
|
||||
.left-1 {
|
||||
left: calc(var(--spacing) * 1);
|
||||
}
|
||||
.left-1\/2 {
|
||||
left: calc(1/2 * 100%);
|
||||
}
|
||||
|
|
@ -624,9 +609,6 @@
|
|||
.col-span-2 {
|
||||
grid-column: span 2 / span 2;
|
||||
}
|
||||
.col-span-12 {
|
||||
grid-column: span 12 / span 12;
|
||||
}
|
||||
.float-end {
|
||||
float: inline-end;
|
||||
}
|
||||
|
|
@ -918,9 +900,6 @@
|
|||
.aspect-video {
|
||||
aspect-ratio: var(--aspect-video);
|
||||
}
|
||||
.h-0 {
|
||||
height: calc(var(--spacing) * 0);
|
||||
}
|
||||
.h-0\.5 {
|
||||
height: calc(var(--spacing) * 0.5);
|
||||
}
|
||||
|
|
@ -1095,6 +1074,9 @@
|
|||
.w-75 {
|
||||
width: calc(var(--spacing) * 75);
|
||||
}
|
||||
.w-80 {
|
||||
width: calc(var(--spacing) * 80);
|
||||
}
|
||||
.w-100 {
|
||||
width: calc(var(--spacing) * 100);
|
||||
}
|
||||
|
|
@ -1161,9 +1143,6 @@
|
|||
.shrink {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
.shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.flex-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
|
@ -1185,16 +1164,9 @@
|
|||
.border-collapse {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.border-separate {
|
||||
border-collapse: separate;
|
||||
}
|
||||
.origin-top {
|
||||
transform-origin: top;
|
||||
}
|
||||
.-translate-x-1 {
|
||||
--tw-translate-x: calc(var(--spacing) * -1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.-translate-x-1\/2 {
|
||||
--tw-translate-x: calc(calc(1/2 * 100%) * -1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
|
|
@ -1207,10 +1179,6 @@
|
|||
--tw-translate-x: calc(var(--spacing) * 16);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.-translate-y-1 {
|
||||
--tw-translate-y: calc(var(--spacing) * -1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
}
|
||||
.-translate-y-1\/2 {
|
||||
--tw-translate-y: calc(calc(1/2 * 100%) * -1);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
|
|
@ -1289,9 +1257,6 @@
|
|||
.grid-cols-4 {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
}
|
||||
.grid-cols-12 {
|
||||
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||||
}
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
|
@ -1397,6 +1362,13 @@
|
|||
margin-block-end: calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)));
|
||||
}
|
||||
}
|
||||
.space-x-1 {
|
||||
:where(& > :not(:last-child)) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-inline-start: calc(calc(var(--spacing) * 1) * var(--tw-space-x-reverse));
|
||||
margin-inline-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-x-reverse)));
|
||||
}
|
||||
}
|
||||
.space-x-2 {
|
||||
:where(& > :not(:last-child)) {
|
||||
--tw-space-x-reverse: 0;
|
||||
|
|
@ -1651,9 +1623,6 @@
|
|||
border-color: color-mix(in oklab, var(--color-cyan-600) 50%, transparent);
|
||||
}
|
||||
}
|
||||
.border-cyan-700 {
|
||||
border-color: var(--color-cyan-700);
|
||||
}
|
||||
.border-cyan-700\/50 {
|
||||
border-color: color-mix(in srgb, oklch(52% 0.105 223.128) 50%, transparent);
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
|
|
@ -1693,9 +1662,6 @@
|
|||
.border-green-400 {
|
||||
border-color: var(--color-green-400);
|
||||
}
|
||||
.border-neutral-200 {
|
||||
border-color: var(--color-neutral-200);
|
||||
}
|
||||
.border-orange-200 {
|
||||
border-color: var(--color-orange-200);
|
||||
}
|
||||
|
|
@ -1807,9 +1773,6 @@
|
|||
.bg-blue-600 {
|
||||
background-color: var(--color-blue-600);
|
||||
}
|
||||
.bg-current {
|
||||
background-color: currentcolor;
|
||||
}
|
||||
.bg-cyan-50 {
|
||||
background-color: var(--color-cyan-50);
|
||||
}
|
||||
|
|
@ -1825,6 +1788,9 @@
|
|||
.bg-cyan-500 {
|
||||
background-color: var(--color-cyan-500);
|
||||
}
|
||||
.bg-cyan-600 {
|
||||
background-color: var(--color-cyan-600);
|
||||
}
|
||||
.bg-cyan-700 {
|
||||
background-color: var(--color-cyan-700);
|
||||
}
|
||||
|
|
@ -2247,6 +2213,10 @@
|
|||
--tw-gradient-to: var(--color-emerald-600);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
}
|
||||
.to-gray-50 {
|
||||
--tw-gradient-to: var(--color-gray-50);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
}
|
||||
.to-gray-100 {
|
||||
--tw-gradient-to: var(--color-gray-100);
|
||||
--tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position));
|
||||
|
|
@ -2571,6 +2541,9 @@
|
|||
.pl-9 {
|
||||
padding-left: calc(var(--spacing) * 9);
|
||||
}
|
||||
.pl-10 {
|
||||
padding-left: calc(var(--spacing) * 10);
|
||||
}
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
@ -2694,9 +2667,6 @@
|
|||
.break-all {
|
||||
word-break: break-all;
|
||||
}
|
||||
.whitespace-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.text-amber-400 {
|
||||
color: var(--color-amber-400);
|
||||
}
|
||||
|
|
@ -2796,9 +2766,6 @@
|
|||
.text-green-900 {
|
||||
color: var(--color-green-900);
|
||||
}
|
||||
.text-neutral-800 {
|
||||
color: var(--color-neutral-800);
|
||||
}
|
||||
.text-orange-100 {
|
||||
color: var(--color-orange-100);
|
||||
}
|
||||
|
|
@ -2826,6 +2793,9 @@
|
|||
.text-purple-900 {
|
||||
color: var(--color-purple-900);
|
||||
}
|
||||
.text-red-400 {
|
||||
color: var(--color-red-400);
|
||||
}
|
||||
.text-red-500 {
|
||||
color: var(--color-red-500);
|
||||
}
|
||||
|
|
@ -3085,10 +3055,6 @@
|
|||
--tw-outline-style: none;
|
||||
outline-style: none;
|
||||
}
|
||||
.select-all {
|
||||
-webkit-user-select: all;
|
||||
user-select: all;
|
||||
}
|
||||
.select-none {
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
|
|
@ -3228,6 +3194,13 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.hover\:border-cyan-300 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
border-color: var(--color-cyan-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:border-cyan-400 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
|
|
@ -3284,6 +3257,13 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-cyan-50 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: var(--color-cyan-50);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-cyan-500 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
|
|
@ -3298,6 +3278,13 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-cyan-700 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
background-color: var(--color-cyan-700);
|
||||
}
|
||||
}
|
||||
}
|
||||
.hover\:bg-gray-50 {
|
||||
&:hover {
|
||||
@media (hover: hover) {
|
||||
|
|
@ -3700,6 +3687,20 @@
|
|||
max-width: var(--container-md);
|
||||
}
|
||||
}
|
||||
.sm\:flex-row {
|
||||
@media (width >= 40rem) {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
.sm\:space-y-0 {
|
||||
@media (width >= 40rem) {
|
||||
:where(& > :not(:last-child)) {
|
||||
--tw-space-y-reverse: 0;
|
||||
margin-block-start: calc(calc(var(--spacing) * 0) * var(--tw-space-y-reverse));
|
||||
margin-block-end: calc(calc(var(--spacing) * 0) * calc(1 - var(--tw-space-y-reverse)));
|
||||
}
|
||||
}
|
||||
}
|
||||
.sm\:space-y-6 {
|
||||
@media (width >= 40rem) {
|
||||
:where(& > :not(:last-child)) {
|
||||
|
|
@ -3709,6 +3710,15 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.sm\:space-x-4 {
|
||||
@media (width >= 40rem) {
|
||||
:where(& > :not(:last-child)) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-inline-start: calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse));
|
||||
margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse)));
|
||||
}
|
||||
}
|
||||
}
|
||||
.sm\:rounded-3xl {
|
||||
@media (width >= 40rem) {
|
||||
border-radius: var(--radius-3xl);
|
||||
|
|
@ -3972,16 +3982,6 @@
|
|||
padding-inline: calc(var(--spacing) * 8);
|
||||
}
|
||||
}
|
||||
.dark\:border-neutral-600 {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
border-color: var(--color-neutral-600);
|
||||
}
|
||||
}
|
||||
.dark\:text-white {
|
||||
@media (prefers-color-scheme: dark) {
|
||||
color: var(--color-white);
|
||||
}
|
||||
}
|
||||
}
|
||||
@property --tw-translate-x {
|
||||
syntax: "*";
|
||||
|
|
|
|||
|
|
@ -1,353 +1,523 @@
|
|||
document.addEventListener("DOMContentLoaded", function () {
|
||||
initializePengolahanDataTable();
|
||||
});
|
||||
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;
|
||||
|
||||
// helper: parse date string in format dd/mm/yyyy to a Date object (local)
|
||||
function parseDateDMY(dateStr) {
|
||||
if (!dateStr) return new Date(dateStr);
|
||||
// accept either dd/mm/yyyy or ISO
|
||||
if (dateStr.indexOf("/") !== -1) {
|
||||
const parts = dateStr.split("/");
|
||||
// parts[0]=dd, [1]=mm, [2]=yyyy
|
||||
const d = parseInt(parts[0], 10);
|
||||
const m = parseInt(parts[1], 10) - 1;
|
||||
const y = parseInt(parts[2], 10);
|
||||
return new Date(y, m, d);
|
||||
this.init();
|
||||
}
|
||||
return new Date(dateStr);
|
||||
}
|
||||
|
||||
function initializePengolahanDataTable() {
|
||||
if (!document.getElementById("pengolahanTable")) return;
|
||||
init() {
|
||||
this.injectStyles();
|
||||
this.createTableStructure();
|
||||
this.createPaginationControls();
|
||||
this.createSearchInput();
|
||||
this.bindEvents();
|
||||
this.loadData();
|
||||
}
|
||||
|
||||
if (typeof $ !== "undefined" && $.fn.DataTable) {
|
||||
$("#pengolahanTable").DataTable({
|
||||
ajax: {
|
||||
url: "/api/web/jasa/olah",
|
||||
type: "POST",
|
||||
dataSrc: function (json) {
|
||||
if (!json) return [];
|
||||
return json.data || json;
|
||||
},
|
||||
error: function (xhr, error, thrown) {
|
||||
console.error("Error fetching pengolahan data:", error || thrown);
|
||||
},
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
data: null,
|
||||
className: "px-3 py-4 text-sm text-gray-900 text-center font-medium",
|
||||
title: "No",
|
||||
width: "5%",
|
||||
render: function (data, type, row, meta) {
|
||||
return meta.row + 1;
|
||||
},
|
||||
},
|
||||
{
|
||||
data: "nama",
|
||||
className: "px-3 py-4 text-sm text-gray-900 font-medium break-words",
|
||||
title: "Nama Perusahaan",
|
||||
width: "25%",
|
||||
},
|
||||
{
|
||||
data: "alamat",
|
||||
className: "px-3 py-4 text-sm text-gray-700 break-words",
|
||||
title: "Alamat",
|
||||
width: "50%",
|
||||
},
|
||||
{
|
||||
data: "tglBerlakuIzin",
|
||||
className: "px-3 py-4 text-sm text-gray-900 text-center",
|
||||
title: "Berlaku Hingga",
|
||||
width: "20%",
|
||||
render: function (data, type, row) {
|
||||
if (type === "display") {
|
||||
const date = parseDateDMY(data);
|
||||
const formattedDate = date.toLocaleDateString("id-ID", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
});
|
||||
|
||||
const today = new Date();
|
||||
const expiry = parseDateDMY(data);
|
||||
const daysUntilExpiry = Math.ceil(
|
||||
(expiry - today) / (1000 * 60 * 60 * 24)
|
||||
);
|
||||
|
||||
let statusClass = "bg-green-100 text-green-800";
|
||||
if (daysUntilExpiry < 30) {
|
||||
statusClass = "bg-red-100 text-red-800";
|
||||
} else if (daysUntilExpiry < 90) {
|
||||
statusClass = "bg-yellow-100 text-yellow-800";
|
||||
}
|
||||
|
||||
return `<div class="text-center">
|
||||
<div class="text-sm font-medium text-gray-900 mb-1">${formattedDate}</div>
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${statusClass}">
|
||||
${
|
||||
daysUntilExpiry > 0
|
||||
? `${daysUntilExpiry} hari`
|
||||
: "Expired"
|
||||
}
|
||||
</span>
|
||||
</div>`;
|
||||
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;
|
||||
}
|
||||
return data;
|
||||
},
|
||||
},
|
||||
],
|
||||
language: {
|
||||
search: "Cari:",
|
||||
lengthMenu: "Tampilkan _MENU_ data per halaman",
|
||||
info: "Menampilkan _START_ sampai _END_ dari _TOTAL_ data",
|
||||
infoEmpty: "Menampilkan 0 sampai 0 dari 0 data",
|
||||
infoFiltered: "(disaring dari _MAX_ total data)",
|
||||
paginate: {
|
||||
first: "Pertama",
|
||||
last: "Terakhir",
|
||||
next: "Selanjutnya",
|
||||
previous: "Sebelumnya",
|
||||
},
|
||||
emptyTable: "Tidak ada data yang tersedia",
|
||||
zeroRecords: "Tidak ditemukan data yang sesuai",
|
||||
},
|
||||
pageLength: 10,
|
||||
responsive: false,
|
||||
ordering: true,
|
||||
searching: true,
|
||||
paging: true,
|
||||
info: true,
|
||||
autoWidth: false,
|
||||
scrollX: false,
|
||||
layout: {
|
||||
topStart: {
|
||||
pageLength: {
|
||||
text: "Tampilkan _MENU_ data",
|
||||
},
|
||||
},
|
||||
topEnd: {
|
||||
search: {
|
||||
placeholder: "Cari perusahaan pengolahan...",
|
||||
},
|
||||
},
|
||||
bottomStart: "info",
|
||||
bottomEnd: "paging",
|
||||
},
|
||||
initComplete: function () {
|
||||
$("#loadingIndicator").hide();
|
||||
|
||||
const wrapper = $(".dataTables_wrapper");
|
||||
#pagination button {
|
||||
touch-action: manipulation;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
wrapper
|
||||
.find('input[type="search"]')
|
||||
.addClass(
|
||||
"block w-full px-4 py-2 text-sm border border-gray-300 rounded-lg bg-white focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200"
|
||||
)
|
||||
.attr("placeholder", "Cari perusahaan pengolahan...");
|
||||
const existingStyles = document.getElementById("custom-table-styles");
|
||||
if (existingStyles) {
|
||||
existingStyles.remove();
|
||||
}
|
||||
document.head.insertAdjacentHTML("beforeend", styles);
|
||||
}
|
||||
|
||||
wrapper
|
||||
.find("select")
|
||||
.addClass(
|
||||
"px-3 py-2 text-sm border border-gray-300 rounded-lg bg-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||
);
|
||||
createTableStructure() {
|
||||
const table = document.getElementById(this.tableId);
|
||||
table.classList.add("modern-table");
|
||||
|
||||
wrapper
|
||||
.find(".dt-paging .dt-paging-button")
|
||||
.addClass(
|
||||
"px-3 py-2 mx-1 text-sm border border-gray-300 rounded-md bg-white text-gray-700 hover:bg-blue-50 hover:text-blue-600 hover:border-blue-300 transition-all duration-200"
|
||||
);
|
||||
let tbody = table.querySelector("tbody");
|
||||
if (tbody) {
|
||||
tbody.id = `${this.tableId}-tbody`;
|
||||
}
|
||||
|
||||
wrapper
|
||||
.find(".dt-paging .dt-paging-button.current")
|
||||
.addClass("bg-blue-600 text-white border-blue-600 hover:bg-blue-700")
|
||||
.removeClass("bg-white text-gray-700");
|
||||
this.showLoading();
|
||||
}
|
||||
|
||||
wrapper
|
||||
.find(".dt-paging .dt-paging-button.disabled")
|
||||
.addClass("opacity-50 cursor-not-allowed");
|
||||
createSearchInput() {}
|
||||
|
||||
if (window.innerWidth <= 768) {
|
||||
wrapper.find(".dt-paging").addClass("text-center");
|
||||
wrapper.find(".dt-info").addClass("text-center text-sm");
|
||||
wrapper.find(".dt-length").addClass("text-center");
|
||||
wrapper.find(".dt-search").addClass("text-center");
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
wrapper.find(".dt-length").addClass("flex items-center space-x-2");
|
||||
wrapper.find(".dt-search").addClass("flex items-center space-x-2");
|
||||
wrapper.find(".dt-info").addClass("text-sm text-gray-600");
|
||||
searchInput.addEventListener("input", (e) => {
|
||||
clearTimeout(this.searchTimeout);
|
||||
this.searchTimeout = setTimeout(() => {
|
||||
this.searchTerm = searchInput.value;
|
||||
this.currentPage = 1;
|
||||
this.draw++;
|
||||
this.loadData();
|
||||
}, 500);
|
||||
});
|
||||
}
|
||||
|
||||
$("#pengolahanTable").removeClass("dataTable").addClass("w-full");
|
||||
$("#pengolahanTable thead th").addClass(
|
||||
"bg-cyan-400 text-white font-semibold text-sm uppercase tracking-wider border-b"
|
||||
);
|
||||
|
||||
wrapper.find(".dtr-control").remove();
|
||||
|
||||
$("#pengolahanTable tbody tr").addClass("transition-all duration-200");
|
||||
|
||||
$(window).on("resize", function () {
|
||||
const wrapper = $(".dataTables_wrapper");
|
||||
if (window.innerWidth <= 768) {
|
||||
wrapper.find(".dt-paging").addClass("text-center");
|
||||
wrapper.find(".dt-info").addClass("text-center text-sm");
|
||||
wrapper.find(".dt-length").addClass("text-center");
|
||||
wrapper.find(".dt-search").addClass("text-center");
|
||||
} else {
|
||||
wrapper.find(".dt-paging").removeClass("text-center");
|
||||
wrapper.find(".dt-info").removeClass("text-center text-sm");
|
||||
wrapper.find(".dt-length").removeClass("text-center");
|
||||
wrapper.find(".dt-search").removeClass("text-center");
|
||||
}
|
||||
});
|
||||
},
|
||||
drawCallback: function () {
|
||||
$("#pengolahanTable tbody tr")
|
||||
.removeClass("bg-blue-50")
|
||||
.hover(
|
||||
function () {
|
||||
$(this).addClass(
|
||||
"bg-blue-50 shadow-sm transform transition-all duration-200"
|
||||
);
|
||||
},
|
||||
function () {
|
||||
$(this).removeClass("bg-blue-50 shadow-sm transform");
|
||||
}
|
||||
);
|
||||
|
||||
$("#pengolahanTable tbody tr:even").addClass("bg-gray-50");
|
||||
$("#pengolahanTable tbody tr:odd").addClass("bg-white");
|
||||
},
|
||||
});
|
||||
} else {
|
||||
loadPengolahanDataManually();
|
||||
if (pageSizeSelect) {
|
||||
pageSizeSelect.addEventListener("change", (e) => {
|
||||
this.pageSize = parseInt(e.target.value);
|
||||
this.currentPage = 1;
|
||||
this.draw++;
|
||||
this.loadData();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loadPengolahanDataManually() {
|
||||
fetch("/website/data/pengolahan-data.json")
|
||||
.then((response) => {
|
||||
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}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((data) => {
|
||||
const tbody = document.querySelector("#pengolahanTable tbody");
|
||||
tbody.innerHTML = "";
|
||||
|
||||
data.forEach((item, index) => {
|
||||
const row = document.createElement("tr");
|
||||
row.className = `${
|
||||
index % 2 === 0 ? "bg-gray-50" : "bg-white"
|
||||
} hover:bg-blue-50 transition-all duration-200`;
|
||||
const result = await response.json();
|
||||
|
||||
const date = parseDateDMY(item.tglBerlakuIzin);
|
||||
const formattedDate = date.toLocaleDateString("id-ID", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
});
|
||||
this.data = result.data || [];
|
||||
this.totalRecords = result.recordsTotal || 0;
|
||||
this.filteredRecords = result.recordsFiltered || 0;
|
||||
|
||||
const today = new Date();
|
||||
const expiry = new Date(item.tglBerlakuIzin);
|
||||
const daysUntilExpiry = Math.ceil(
|
||||
(expiry - 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";
|
||||
}
|
||||
|
||||
row.innerHTML = `
|
||||
<td class="px-3 py-4 text-sm text-gray-900 text-center font-medium" style="width: 5%">${
|
||||
index + 1
|
||||
}</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-900 font-medium break-words" style="width: 25%">${
|
||||
item.nama
|
||||
}</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-700 break-words" style="width: 50%">${
|
||||
item.alamat
|
||||
}</td>
|
||||
<td class="px-3 py-4 text-sm text-gray-900 text-center" style="width: 20%">
|
||||
<div class="text-sm font-medium text-gray-900">${formattedDate}</div>
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium ${statusClass} mt-1">
|
||||
${statusText}
|
||||
</span>
|
||||
</td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(row);
|
||||
console.log("Data loaded successfully:", {
|
||||
dataLength: this.data.length,
|
||||
totalRecords: this.totalRecords,
|
||||
filteredRecords: this.filteredRecords,
|
||||
});
|
||||
|
||||
const loadingIndicator = document.getElementById("loadingIndicator");
|
||||
if (loadingIndicator) {
|
||||
loadingIndicator.style.display = "none";
|
||||
}
|
||||
|
||||
addManualSearch(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.renderTable();
|
||||
this.renderPagination();
|
||||
this.hideLoading();
|
||||
} catch (error) {
|
||||
console.error("Error loading data:", error);
|
||||
const loadingIndicator = document.getElementById("loadingIndicator");
|
||||
if (loadingIndicator) {
|
||||
loadingIndicator.innerHTML = `
|
||||
<div class="text-red-500 text-center">
|
||||
<i class="fas fa-exclamation-triangle mr-2"></i>
|
||||
Error memuat data: ${error.message}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
});
|
||||
}
|
||||
this.showError("Gagal memuat data. Silakan coba lagi.");
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function addManualSearch(data) {
|
||||
const tableContainer = document
|
||||
.querySelector("#pengolahanTable")
|
||||
.closest(".bg-white");
|
||||
if (!document.getElementById("manualSearchInput")) {
|
||||
const searchContainer = document.createElement("div");
|
||||
searchContainer.className = "mb-4 flex justify-end";
|
||||
searchContainer.innerHTML = `
|
||||
<div class="relative">
|
||||
<input type="text" id="manualSearchInput" placeholder="Cari perusahaan pengolahan..."
|
||||
class="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent pr-10">
|
||||
<div class="absolute inset-y-0 right-0 pr-3 flex items-center">
|
||||
<svg class="h-5 w-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
showLoading() {
|
||||
const loadingIndicator = document.getElementById("loadingIndicator");
|
||||
const emptyState = document.getElementById("emptyState");
|
||||
const tableContainer = document.querySelector(".overflow-x-auto");
|
||||
const pagination = document.getElementById("pagination");
|
||||
|
||||
tableContainer.insertBefore(
|
||||
searchContainer,
|
||||
document.querySelector(".overflow-x-auto")
|
||||
);
|
||||
if (loadingIndicator) loadingIndicator.classList.remove("hidden");
|
||||
if (emptyState) emptyState.classList.add("hidden");
|
||||
if (tableContainer) tableContainer.style.display = "none";
|
||||
|
||||
document
|
||||
.getElementById("manualSearchInput")
|
||||
.addEventListener("input", function (e) {
|
||||
const searchTerm = e.target.value.toLowerCase();
|
||||
const rows = document.querySelectorAll("#pengolahanTable tbody tr");
|
||||
|
||||
rows.forEach((row) => {
|
||||
const text = row.textContent.toLowerCase();
|
||||
if (text.includes(searchTerm)) {
|
||||
row.style.display = "";
|
||||
} else {
|
||||
row.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"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue