MIF_E31231060/resources/views/monitoring-prediksi.blade.php

2330 lines
74 KiB
PHP

<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MONITORING PREDIKSI - UDD PMI KABUPATEN JEMBER</title>
<link rel="icon" type="image/png" href="{{ asset('images/logo-pmi.png') }}">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body,
input,
button,
select,
textarea {
font-family: 'Poppins', sans-serif;
}
body {
background: white;
color: #1e293b;
min-height: 100vh;
padding: 32px;
overflow-x: clip;
}
.container {
max-width: 1700px;
margin: auto;
background: white;
backdrop-filter: blur(16px);
border: 1px solid #d32f2fce;
border-radius: 10px;
padding: 30px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.27);
}
.top-banner {
background:
linear-gradient(135deg, rgba(211, 47, 47, .95), rgba(127, 0, 0, .96));
border-radius: 10px;
padding: 20px;
margin-bottom: 24px;
display: flex;
justify-content: space-between;
align-items: center;
color: white;
box-shadow:
0 10px 30px rgba(220, 38, 38, 0.25);
}
.banner-left {
display: flex;
align-items: center;
gap: 18px;
}
.banner-subtitle {
font-size: 12px;
opacity: .85;
letter-spacing: 1px;
margin-bottom: 4px;
}
.banner-title {
font-size: 20px;
font-weight: 700;
}
.banner-icon {
position: relative;
width: 52px;
height: 52px;
min-width: 52px;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, .96);
font-size: 22px;
background: linear-gradient(135deg, #8b5cf6, #7c3aed);
border: 1px solid rgba(255, 255, 255, .18);
backdrop-filter: blur(12px);
box-shadow:
inset 0 1px 1px rgba(255, 255, 255, .24),
inset 0 -1px 1px rgba(255, 255, 255, .05),
0 10px 18px rgba(0, 0, 0, .16),
0 4px 8px rgba(0, 0, 0, .10);
overflow: hidden;
transition: all .25s ease;
}
.banner-icon::before {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(to bottom right,
rgba(255, 255, 255, .18),
transparent 60%);
pointer-events: none;
}
.header {
margin-bottom: 24px;
}
.header h1 {
font-size: 28px;
font-weight: 700;
}
.header p {
margin-top: 6px;
color: #64748b;
font-size: 14px;
}
.header-nav {
display: flex;
gap: 10px;
width: 800px;
}
.header-btn {
flex: 1;
display: flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
border-radius: 5px;
text-decoration: none;
font-size: 13px;
font-weight: 600;
transition: .25s ease;
border: 1px solid rgba(255, 255, 255, .15);
color: white;
}
.header-btn:nth-child(1) {
background: linear-gradient(135deg,
#5b8def,
#3c6fd7);
}
.header-btn:nth-child(1):hover {
background: linear-gradient(135deg,
#6e9fff,
#4b84f7);
}
.header-btn:nth-child(1) i {
color: #eaf2ff;
}
.header-btn:nth-child(2) {
background: linear-gradient(135deg,
#d98a3d,
#c97828);
}
.header-btn:nth-child(2):hover {
background: linear-gradient(135deg,
#e69c5a,
#d98a3d);
}
.header-btn:nth-child(2) i {
color: #fff4e8;
}
.header-btn:nth-child(3) {
background: linear-gradient(135deg,
#22c55e,
#15803d);
}
.header-btn:nth-child(3):hover {
background: linear-gradient(135deg,
#34d399,
#16a34a);
}
.header-btn:nth-child(3) i {
color: #fff1f1;
}
.header-btn:nth-child(4) {
background: linear-gradient(135deg,
#8b5cf6,
#7c3aed);
}
.header-btn:nth-child(4) i {
color: #f3e8ff;
}
.header-btn:hover {
transform: translateY(-2px);
filter: brightness(1.05);
color: white;
}
.header-btn.active {
pointer-events: none;
cursor: default;
}
.refresh-btn {
min-width: 45px;
align-self: stretch;
border: none;
border-radius: 5px;
background:
linear-gradient(135deg,
#ef4444,
#b91c1c);
color: white;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: .25s;
}
.refresh-icon {
font-size: 20px;
transition: transform .3s ease;
}
.refresh-btn:hover .refresh-icon {
transform: rotate(180deg);
}
.header-left i,
.header-btn i {
position: relative;
font-size: 18px;
width: 40px;
height: 40px;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
color: rgba(255, 255, 255, .96);
background: linear-gradient(145deg,
rgba(255, 255, 255, .22),
rgba(255, 255, 255, .08));
border: 1px solid rgba(255, 255, 255, .18);
backdrop-filter: blur(10px);
box-shadow:
inset 0 1px 1px rgba(255, 255, 255, .24),
inset 0 -1px 1px rgba(255, 255, 255, .05),
0 10px 18px rgba(0, 0, 0, .16),
0 4px 8px rgba(0, 0, 0, .10);
}
.dashboard-grid {
display: grid;
grid-template-columns: 2fr 0.8fr;
gap: 20px;
align-items: stretch;
padding-top: 15px;
}
.right-side {
display: flex;
flex-direction: column;
height: 100%;
align-self: stretch;
}
.donut-card {
flex: 1;
display: flex;
flex-direction: column;
height: auto;
}
.left-side {
display: flex;
flex-direction: column;
}
.summary-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 18px;
margin-bottom: 24px;
}
.summary-card {
background: white;
border-radius: 10px;
padding: 22px;
border: 1px solid rgba(0, 0, 0, 0.09);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08);
transition: .25s;
position: relative;
overflow: hidden;
}
.summary-card::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 5px;
background:
linear-gradient(to right, #dc2626, #ef4444);
}
.summary-icon {
position: absolute;
top: 18px;
right: 18px;
width: 46px;
height: 46px;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
background: rgba(220, 38, 38, 0.08);
color: #dc2626;
}
.summary-icon.range {
background: rgba(124, 58, 237, 0.12);
color: #6d28d9;
}
.summary-icon.warning {
background: rgba(245, 158, 11, 0.12);
color: #d97706;
}
.summary-icon.primary {
background: rgba(34, 197, 94, 0.12);
color: #16a34a;
}
.summary-icon.danger {
background: rgba(37, 99, 235, 0.1);
color: #2563eb;
}
.summary-card:hover {
transform: translateY(-3px);
}
.summary-card .title {
font-size: 14px;
color: #64748b;
margin-bottom: 10px;
}
.summary-card .value {
font-size: 27px;
font-weight: 700;
}
.summary-card .desc {
margin-top: 10px;
font-size: 13px;
color: #94a3b8;
}
.card {
background: white;
border-radius: 10px;
padding: 24px;
margin-bottom: 24px;
border: 1px solid rgba(0, 0, 0, 0.09);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.08);
}
.section-title {
font-size: 18px;
font-weight: 700;
margin-bottom: 20px;
color: #8f0000;
}
#periodeText {
color: #ef4444;
margin-left: 6px;
}
.filters {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-areas:
"periode jenis status btnFilter"
"search search search btnPrint";
gap: 14px;
align-items: stretch;
}
.filters input,
.filters select {
padding: 12px 14px;
border-radius: 8px;
border: 1px solid #dbe2ea;
background: #f8fafc;
outline: none;
font-size: 14px;
}
.filters select {
padding: 12px 14px;
border-radius: 8px;
border: 1px solid #dbe2ea;
background: white;
font-size: 14px;
cursor: pointer;
transition: all .25s ease;
box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.06);
background-repeat: no-repeat;
padding-right: 38px;
}
.filters select:hover {
border-color: #cbd5e1;
}
.filters button {
border: none;
background: #2563eb;
color: white;
border-radius: 12px;
padding: 12px;
font-weight: 600;
cursor: pointer;
}
.filter-action {
position: relative;
grid-area: btnFilter;
display: flex;
flex-direction: column;
justify-content: center;
}
.filter-action button {
width: 100%;
height: 100%;
}
.filters input[name="search"] {
padding: 12px 14px;
border-radius: 8px;
border: 1px solid #dbe2ea;
background: white;
font-size: 14px;
outline: none;
transition: all .25s ease;
box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.11);
}
.filters input[name="search"]::placeholder {
color: #94a3b8;
}
.filters input[name="search"]:hover {
border-color: #cbd5e1;
}
#periode {
grid-area: periode;
}
#jenis {
grid-area: jenis;
}
#status {
grid-area: status;
}
.filter-action {
grid-area: btnFilter;
}
.filters input[name="search"] {
grid-area: search;
}
#btnPrint {
grid-area: btnPrint;
}
.note-periode {
position: absolute;
top: -22px;
left: 0;
font-size: 12px;
color: #ef4444;
white-space: nowrap;
display: none;
}
.table-wrapper {
overflow-x: auto;
overflow-y: hidden;
width: 100%;
max-width: 100%;
-webkit-overflow-scrolling: touch;
border-left: 1px solid #e2e8f0;
border-right: 1px solid #e2e8f0;
border-radius: 4px;
}
.table-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 18px;
gap: 16px;
flex-wrap: wrap;
}
.table-info {
font-size: 13px;
color: #64748b;
}
.pagination {
display: flex;
align-items: center;
gap: 8px;
}
.pagination button {
min-width: 38px;
height: 38px;
border: none;
border-radius: 8px;
background: #fff5f5;
color: #b91c1c;
font-weight: 600;
cursor: pointer;
transition: .2s ease;
border: 1px solid #fecaca;
}
.pagination button:hover {
background: #fee2e2;
}
.pagination button.active {
background: linear-gradient(135deg, #d32f2f, #9a0007);
color: white;
border-color: transparent;
box-shadow: 0 6px 16px rgba(211, 47, 47, 0.25);
}
.pagination button:disabled {
opacity: .45;
cursor: not-allowed;
}
.table-title-flex {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.table-title-right {
font-size: 13px;
color: #64748b;
font-weight: 500;
}
.footer-red {
margin-top: 30px;
background: linear-gradient(135deg, #d32f2f, #7f0000);
color: white;
border-radius: 10px;
padding: 18px 22px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 13px;
box-shadow: 0 10px 30px rgba(211, 47, 47, 0.22);
flex-wrap: wrap;
gap: 10px;
}
.footer-red strong {
font-weight: 700;
}
.sticky-col {
position: sticky;
background: #fff7f7;
z-index: 5;
}
.sticky-kode {
left: 0;
min-width: 140px;
max-width: 140px;
}
.sticky-nama {
left: 140px;
min-width: 260px;
max-width: 260px;
}
thead .sticky-col {
z-index: 10;
background: linear-gradient(135deg, #ffdcdc, #ffe7e7);
}
tbody tr:nth-child(even) .sticky-col {
background: #fff1f1;
}
tbody tr:nth-child(odd) .sticky-col {
background: #fff7f7;
}
.sticky-shadow {
box-shadow: 4px 0 10px rgba(211, 47, 47, 0.12);
border-right: 1px solid #f5c2c2;
}
table {
width: max-content;
min-width: 1600px;
border-collapse: collapse;
}
th,
td {
white-space: nowrap;
}
th {
background: linear-gradient(135deg, #ffdcdc, #ffe7e7);
color: #9a0007;
padding: 16px 14px;
text-align: left;
font-size: 11.5px;
text-transform: uppercase;
letter-spacing: .5px;
border-bottom: 1px solid #e2e8f0;
}
th {
border-right: 1px solid rgba(255, 255, 255, 0.2);
}
th:last-child {
border-right: none;
}
td {
padding: 16px 14px;
border-bottom: 1px solid #f1f5f9;
font-size: 13px;
}
tr:hover {
background: #f8fafc;
}
tbody tr:nth-child(odd) {
background: #ffffff;
}
tbody tr:nth-child(even) {
background: #fafafa;
}
.badge {
padding: 6px 12px;
border-radius: 5px;
font-size: 12px;
font-weight: 600;
display: inline-block;
}
.kode-badge {
display: inline-block;
padding: 4px 10px;
border-radius: 5px;
font-size: 12px;
font-weight: 600;
background: #fee2e2;
color: #b91c1c;
white-space: nowrap;
}
.green {
background: #dcfce7;
color: #166534;
}
.yellow {
background: #fef9c3;
color: #854d0e;
}
.red {
background: #fee2e2;
color: #991b1b;
}
.blue {
background: #dbeafe;
color: #1d4ed8;
}
.gray {
background: #e2e8f0;
color: #475569;
}
.chart-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 20px;
}
.chart-box {
background:
linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
border: 1px solid #eef2f7;
border-radius: 22px;
padding: 20px;
height: 260px;
position: relative;
overflow: hidden;
}
.fake-chart {
position: absolute;
bottom: 20px;
left: 20px;
right: 20px;
height: 140px;
display: flex;
align-items: flex-end;
gap: 10px;
}
.bar {
flex: 1;
background:
linear-gradient(to top, #b91c1c, #ef4444);
border-radius: 12px 12px 0 0;
box-shadow:
0 6px 14px rgba(239, 68, 68, 0.25);
}
.list-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 20px;
}
.list-box ul {
list-style: none;
}
.list-box li {
padding: 12px 0;
border-bottom: 1px solid #eef2f7;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 14px;
}
.activity-item {
padding: 14px;
border-radius: 14px;
background: #f8fafc;
margin-bottom: 12px;
}
.activity-item strong {
display: block;
margin-bottom: 5px;
}
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(15, 23, 42, 0.55);
display: none;
align-items: center;
justify-content: center;
z-index: 9999;
}
.modal-box {
width: 380px;
background: rgba(255, 255, 255, 0.92);
backdrop-filter: blur(14px);
border-radius: 18px;
padding: 26px;
text-align: center;
box-shadow: 0 25px 60px rgba(0, 0, 0, 0.25);
animation: modalIn .25s ease;
border: 1px solid rgba(255, 255, 255, 0.3);
}
.filters input.month-input {
position: relative;
width: 100%;
padding: 8px 12px 8px 38px;
font-size: 13px;
border-radius: 8px;
border: 1px solid rgba(211, 47, 47, 0.48);
background:
linear-gradient(145deg, #ffffff, #fff5f5);
background-size: 18px 18px;
box-shadow:
inset 0 2px 6px rgba(0, 0, 0, 0.08),
0 4px 10px rgba(211, 47, 47, 0.08);
}
.filters input.month-input:hover {
border-color: #d32f2f;
box-shadow:
inset 0 2px 6px rgba(0, 0, 0, 0.08),
0 6px 16px rgba(211, 47, 47, 0.15);
}
.filters input.month-input:focus {
outline: none;
border-color: #d32f2f;
background: linear-gradient(145deg, #fff, #ffecec);
box-shadow:
inset 0 2px 6px rgba(0, 0, 0, 0.08),
0 0 0 3px rgba(211, 47, 47, 0.15),
0 10px 25px rgba(211, 47, 47, 0.2);
}
.filters input.month-input::-webkit-calendar-picker-indicator {
opacity: 0;
position: absolute;
right: 0;
width: 100%;
height: 100%;
cursor: pointer;
}
@keyframes modalIn {
from {
transform: translateY(20px) scale(0.95);
opacity: 0;
}
to {
transform: translateY(0) scale(1);
opacity: 1;
}
}
.modal-icon {
width: 64px;
height: 64px;
margin: 0 auto 12px;
border-radius: 50%;
background: rgba(239, 68, 68, 0.1);
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
color: #ef4444;
}
.modal-box h3 {
font-size: 18px;
font-weight: 700;
margin-bottom: 6px;
}
.modal-box p {
font-size: 13px;
color: #64748b;
}
.modal-action {
display: flex;
gap: 10px;
margin-top: 22px;
}
.btn {
border: none;
padding: 9px 14px;
border-radius: 10px;
cursor: pointer;
font-size: 13px;
font-weight: 600;
}
.btn-delete {
background: #d32f2f;
border-radius: 7px;
color: #fff;
cursor: pointer;
transition: all .25s ease;
font-size: 14px;
box-shadow: 0 6px 14px rgba(211, 47, 47, 0.28);
}
.btn-delete:hover {
transform: translateY(-2px) !important;
box-shadow: 0 6px 14px rgba(211, 47, 47, 0.35);
}
.btn-delete:active {
transform: scale(0.9);
}
.btn:hover {
transform: translateY(-1px);
opacity: .95;
}
.btn-filter {
background: linear-gradient(135deg, #d32f2f, #9a0007) !important;
color: white;
border: none;
border-radius: 8px !important;
padding: 12px;
font-weight: 600;
cursor: pointer;
transition: all .25s ease;
box-shadow: 0 10px 25px rgba(211, 47, 47, .3);
}
.btn-filter:hover {
transform: translateY(-2px);
box-shadow: 0 14px 30px rgba(211, 47, 47, .38);
}
.btn-filter:active {
transform: translateY(0px);
box-shadow: 0 6px 15px rgba(211, 47, 47, .25);
}
.btn-filter:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.btn-cancel {
flex: 1;
padding: 10px;
border-radius: 12px;
border: none;
background: #e2e8f0;
font-weight: 600;
cursor: pointer;
transition: .2s;
}
.btn-cancel:hover {
background: #cbd5e1;
}
.btn-confirm {
flex: 1;
padding: 10px;
border-radius: 12px;
border: none;
background: linear-gradient(135deg, #dc2626, #ef4444);
color: white;
font-weight: 600;
cursor: pointer;
box-shadow: 0 8px 20px rgba(239, 68, 68, 0.25);
transition: .2s;
}
.btn-confirm:hover {
transform: translateY(-1px);
opacity: 0.95;
}
#btnFilter {
background: linear-gradient(135deg, #d32f2f, #9a0007) !important;
}
#btnPrint {
background: linear-gradient(135deg, #059669, #10b981) !important;
box-shadow: 0 10px 25px rgba(16, 185, 129, .25);
}
#btnPrint:hover {
box-shadow: 0 14px 30px rgba(16, 185, 129, .35);
}
#btnPrint:active {
box-shadow: 0 6px 15px rgba(16, 185, 129, .2);
}
.sort-icons {
display: inline-flex;
flex-direction: column;
margin-left: 6px;
vertical-align: middle;
}
.sort-icons i {
font-size: 9px;
line-height: 9px;
color: #9a0007;
cursor: pointer;
transition: .2s;
}
.sort-icons i:hover {
transform: scale(1.2);
color: #d32f2f;
}
.sort-icons .active {
color: #d32f2f;
}
.donut-wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
gap: 14px;
}
.donut-svg {
width: 220px;
height: 220px;
transform: rotate(-90deg);
}
.donut-slice {
fill: none;
stroke-width: 40;
cursor: pointer;
transition: .25s ease;
}
.donut-slice:hover {
transform: scale(1.05);
filter: brightness(1.1);
}
.tooltip-donut {
position: fixed;
z-index: 9999;
background: rgba(215, 37, 37, 0.92);
backdrop-filter: blur(10px);
color: #fff;
padding: 10px 14px;
border-radius: 12px;
font-size: 13px;
line-height: 1.4;
pointer-events: none;
opacity: 0;
transform: translate(-50%, -8px) scale(.95);
transition:
opacity .18s ease,
transform .18s ease;
box-shadow:
0 12px 28px rgba(0, 0, 0, .28);
}
.tooltip-donut.show {
opacity: 1;
transform: translate(-50%, -14px) scale(1);
}
.tooltip-row {
display: flex;
align-items: center;
gap: 8px;
}
.tooltip-color {
width: 10px;
height: 10px;
border-radius: 50%;
}
.tooltip-title {
font-weight: 600;
}
.tooltip-value {
font-size: 12px;
opacity: .8;
}
.donut {
width: 220px;
height: 220px;
border-radius: 50%;
background: conic-gradient(#ef4444 0% 35%,
#22c55e 35% 70%,
#f59e0b 70% 100%);
position: relative;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.12);
cursor: pointer;
transition: .25s ease;
}
.donut:hover {
transform: scale(1.03);
}
.donut::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 140px;
height: 140px;
background: white;
border-radius: 50%;
box-shadow: inset 0 3px 10px rgba(0, 0, 0, 0.08);
}
.donut-center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
z-index: 2;
}
.donut-value {
font-size: 22px;
font-weight: 700;
color: #1e293b;
}
.donut-label {
font-size: 12px;
color: #64748b;
}
.donut-legend {
display: flex;
gap: 14px;
font-size: 12px;
color: #475569;
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
display: inline-block;
margin-right: 6px;
}
.dot.red {
background: #2563eb;
}
.dot.green {
background: #22c55e;
}
.dot.yellow {
background: #f59e0b;
}
.table-length-wrapper {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.rows-select {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
padding: 10px 42px 10px 14px;
border-radius: 10px;
border: 1px solid #fecaca;
background:
linear-gradient(135deg, #fff5f5, #ffffff);
color: #b91c1c;
font-size: 13px;
font-weight: 600;
cursor: pointer;
box-shadow:
0 6px 16px rgba(211, 47, 47, 0.08),
inset 0 2px 5px rgba(255, 255, 255, 0.8);
transition: all .25s ease;
background-image:
linear-gradient(45deg, transparent 50%, #b91c1c 50%),
linear-gradient(135deg, #b91c1c 50%, transparent 50%);
background-position:
calc(100% - 18px) calc(50% - 3px),
calc(100% - 12px) calc(50% - 3px);
background-size: 6px 6px;
background-repeat: no-repeat;
}
.rows-select:focus {
outline: none;
border-color: #dc2626;
box-shadow:
0 0 0 4px rgba(220, 38, 38, 0.12),
0 10px 24px rgba(220, 38, 38, 0.12);
}
/* =========================
RESPONSIVE BREAKPOINTS
========================= */
@media (max-width: 992px) {
.top-banner {
flex-direction: column;
align-items: stretch;
gap: 16px;
}
.header-nav {
width: 100%;
}
}
@media (max-width: 768px) {
.header-nav {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
.header-btn {
width: 100%;
}
}
@media (max-width: 480px) {
.header-nav {
grid-template-columns: 1fr;
}
.header-btn {
justify-content: flex-start;
}
}
@media (max-width: 768px) {
.sticky-kode,
.sticky-nama {
position: static;
left: auto;
box-shadow: none;
}
table {
min-width: 1200px;
}
}
@media (min-width: 1800px) {
.container {
max-width: 1900px;
}
.summary-grid {
grid-template-columns: repeat(4, 1fr);
}
}
@media (max-width: 1600px) {
.dashboard-grid {
grid-template-columns: 1.7fr 0.9fr;
}
table {
min-width: 1500px;
}
}
@media (max-width: 1400px) {
body {
padding: 20px;
}
.dashboard-grid {
grid-template-columns: 1fr;
}
.right-side {
width: 100%;
}
.donut-card {
min-height: 380px;
}
table {
min-width: 1400px;
}
}
@media (max-width: 1200px) {
.summary-grid {
grid-template-columns: repeat(2, 1fr);
}
.filters {
grid-template-columns: repeat(2, 1fr);
grid-template-areas:
"periode jenis"
"status search"
"btnFilter btnPrint";
}
.filter-action {
width: 100%;
}
#btnPrint,
#btnFilter {
width: 100%;
}
.table-footer {
flex-direction: column;
align-items: flex-start;
}
}
@media (max-width: 992px) {
body {
padding: 16px;
}
.container {
padding: 20px;
}
.top-banner {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.banner-title {
font-size: 20px;
}
.section-title {
font-size: 17px;
}
.summary-card .value {
font-size: 24px;
}
.donut-svg {
width: 190px;
height: 190px;
}
.table-title-flex {
flex-direction: column;
align-items: flex-start;
}
}
@media (max-width: 768px) {
body {
padding: 12px;
}
.container {
padding: 16px;
}
.summary-grid {
grid-template-columns: 1fr;
}
.filters {
grid-template-columns: 1fr;
grid-template-areas:
"periode"
"jenis"
"status"
"search"
"btnFilter"
"btnPrint";
}
.banner-icon {
width: 46px;
height: 46px;
min-width: 46px;
font-size: 18px;
}
.banner-title {
font-size: 18px;
}
.banner-subtitle {
font-size: 11px;
}
.summary-card {
padding: 18px;
}
.summary-card .value {
font-size: 22px;
}
.summary-icon {
width: 42px;
height: 42px;
font-size: 18px;
}
.donut-svg {
width: 170px;
height: 170px;
}
.donut-legend {
flex-direction: column;
gap: 8px;
align-items: flex-start;
}
.modal-box {
width: 92%;
}
}
@media (max-width: 576px) {
body {
padding: 10px;
}
.container {
padding: 14px;
}
.top-banner {
padding: 16px;
}
.banner-title {
font-size: 16px;
}
.banner-subtitle {
font-size: 10px;
}
.section-title {
font-size: 15px;
}
.summary-card .title {
font-size: 13px;
}
.summary-card .value {
font-size: 20px;
}
.summary-card .desc {
font-size: 11px;
}
.table-info {
font-size: 12px;
}
.pagination {
flex-wrap: wrap;
}
.rows-select {
width: 100%;
}
.footer-red {
flex-direction: column;
align-items: flex-start;
text-align: left;
}
}
@media (max-width: 360px) {
.container {
padding: 12px;
}
.banner-title {
font-size: 15px;
}
.summary-card {
padding: 14px;
}
.summary-card .value {
font-size: 18px;
}
.filters input,
.filters select,
.filters button {
font-size: 12px;
}
.donut-svg {
width: 150px;
height: 150px;
}
}
</style>
</head>
<body class="p-8">
<div class="w-full mx-auto">
<div class="container">
<div class="top-banner">
<div class="banner-left">
<div class="banner-icon">
<i class="fa-solid fa-pie-chart"></i>
</div>
<div>
<div class="banner-title">
MONITORING PREDIKSI
</div>
<div class="banner-subtitle">
Monitoring kondisi prediksi kebutuhan barang habis pakai
</div>
</div>
</div>
<div class="header-nav">
<a href="/prediksi-barang" class="header-btn">
<i class="fa-solid fa-chart-column"></i>
Prediksi
</a>
<a href="/riwayat-prediksi" class="header-btn">
<i class="fa-solid fa-clock-rotate-left"></i>
Riwayat
</a>
<a href="/rencana-pengadaan" class="header-btn">
<i class="fa-solid fa-boxes-stacked"></i>
Pengadaan
</a>
<a href="/monitoring-prediksi" class="header-btn active">
<i class="fa-solid fa-pie-chart"></i>
Monitoring
</a>
<button class="refresh-btn" onclick="location.reload()">
<i class="fa-solid fa-rotate-right refresh-icon"></i>
</button>
</div>
</div>
<div class="dashboard-grid">
<div class="left-side">
<div class="summary-grid" id="summaryWrapper">
<div class="summary-card">
<div class="summary-icon range">
<i class="bi bi-box-seam"></i>
</div>
<div class="title">Total Barang</div>
<div class="value">{{ $totalBarang }}</div>
<div class="desc">Barang habis pakai dalam sistem</div>
</div>
<div class="summary-card">
<div class="summary-icon warning">
<i class="bi bi-exclamation-triangle"></i>
</div>
<div class="title">Belum Dapat Dimonitor</div>
<div class="value">{{ $barangBelumDimonitor }}</div>
<div class="desc">Barang yang belum memiliki data monitoring</div>
</div>
<div class="summary-card">
<div class="summary-icon primary">
<i class="bi bi-graph-up-arrow"></i>
</div>
<div class="title">Data Monitoring</div>
<div class="value">{{ $dataMonitoring }}</div>
<div class="desc">Barang yang memiliki prediksi dan data aktual</div>
</div>
</div>
<div class="card">
<div class="section-title">Filter Monitoring</div>
<form id="filterForm">
@csrf
<div class="filters">
<input type="month" class="month-input" id="periode" name="periode"
value="{{ $filters['periode'] ?? date('Y-m') }}" min="{{ $minPeriode }}"
max="{{ $maxPeriode }}" oninput="cekPeriode()">
<select name="jenis" id="jenis">
<option value="">Semua Jenis</option>
@foreach ($jenisList as $j)
<option value="{{ $j }}"
{{ ($filters['jenis'] ?? '') == $j ? 'selected' : '' }}>
{{ $j }}
</option>
@endforeach
</select>
<select name="status" id="status">
<option value="">Semua Status</option>
<option value="stabil"
{{ ($filters['status'] ?? '') == 'stabil' ? 'selected' : '' }}>
Stabil
</option>
<option value="naik"
{{ ($filters['status'] ?? '') == 'naik' ? 'selected' : '' }}>
Naik
</option>
<option value="turun"
{{ ($filters['status'] ?? '') == 'turun' ? 'selected' : '' }}>
Turun
</option>
</select>
<input type="text" name="search" value="{{ $filters['search'] ?? '' }}"
placeholder="Cari kode / nama barang">
<button type="button" id="btnPrint" class="btn-filter" disabled
style="opacity:0.5;cursor:not-allowed;">
Cetak
</button>
<div class="filter-action">
<button id="btnFilter" type="submit" class="btn-filter" disabled>
Tampilkan
</button>
<div id="notePeriode" class="note-periode">
Tidak ada data
</div>
</div>
</div>
</form>
</div>
</div>
<div class="right-side">
<div class="card donut-card">
<div class="section-title">Distribusi Status Prediksi</div>
<div class="donut-wrapper">
<svg viewBox="0 0 200 200" class="donut-svg">
@php
$radius = 70;
$circumference = 2 * 3.1416 * $radius;
$naikDash = ($persenNaik / 100) * $circumference;
$stabilDash = ($persenStabil / 100) * $circumference;
$turunDash = ($persenTurun / 100) * $circumference;
$naikOffset = 0;
$stabilOffset = -$naikDash;
$turunOffset = -($naikDash + $stabilDash);
@endphp
<defs>
<linearGradient id="gradNaik" x1="0" y1="0" x2="1"
y2="1">
<stop offset="0%" stop-color="#3b82f6" />
<stop offset="100%" stop-color="#1d4ed8" />
</linearGradient>
<linearGradient id="gradStabil" x1="0" y1="0" x2="1"
y2="1">
<stop offset="0%" stop-color="#34d399" />
<stop offset="100%" stop-color="#059669" />
</linearGradient>
<linearGradient id="gradTurun" x1="0" y1="0" x2="1"
y2="1">
<stop offset="0%" stop-color="#fbbf24" />
<stop offset="100%" stop-color="#f59e0b" />
</linearGradient>
<filter id="shadowGlow" x="-50%" y="-50%" width="200%" height="200%">
<feDropShadow dx="0" dy="6" stdDeviation="6"
flood-color="rgba(0,0,0,0.25)" />
</filter>
</defs>
<circle class="donut-slice" data-label="Naik" data-value="{{ $persenNaik }}"
stroke="url(#gradNaik)" filter="url(#shadowGlow)" cx="100" cy="100"
r="70" stroke-dasharray="{{ $naikDash }} {{ $circumference }}"
stroke-dashoffset="{{ $naikOffset }}" />
<circle class="donut-slice" data-label="Stabil" data-value="{{ $persenStabil }}"
stroke="url(#gradStabil)" filter="url(#shadowGlow)" cx="100"
cy="100" r="70"
stroke-dasharray="{{ $stabilDash }} {{ $circumference }}"
stroke-dashoffset="{{ $stabilOffset }}" />
<circle class="donut-slice" data-label="Turun" data-value="{{ $persenTurun }}"
stroke="url(#gradTurun)" filter="url(#shadowGlow)" cx="100" cy="100"
r="70" stroke-dasharray="{{ $turunDash }} {{ $circumference }}"
stroke-dashoffset="{{ $turunOffset }}" />
</svg>
<div class="donut-center"></div>
<div class="donut-legend">
<span class="legend-item" data-index="0">
<span class="dot red"></span> Naik
</span>
<span class="legend-item" data-index="1">
<span class="dot green"></span> Stabil
</span>
<span class="legend-item" data-index="2">
<span class="dot yellow"></span> Turun
</span>
</div>
<div class="tooltip-donut" id="tooltipDonut"></div>
</div>
</div>
</div>
</div>
<div class="card">
<div class="section-title table-title-flex" id="judulTabel">
<div>
Data Monitoring Prediksi
<span id="periodeText" style="font-weight:600;"></span>
</div>
<div class="table-title-right">
<div class="table-length-wrapper">
<span>Tampilkan </span>
<select id="rowsPerPageSelect" class="rows-select">
<option value="10" selected>10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
<span> data</span>
</div>
</div>
</div>
<div class="table-wrapper">
<table>
<thead>
<tr>
<th class="sticky-col sticky-kode">
Kode
<span class="sort-icons">
<i class="fa-solid fa-chevron-up"></i>
<i class="fa-solid fa-chevron-down"></i>
</span>
</th>
<th class="sticky-col sticky-nama sticky-shadow">
Nama Barang
<span class="sort-icons">
<i class="fa-solid fa-chevron-up"></i>
<i class="fa-solid fa-chevron-down"></i>
</span>
</th>
<th>
Jenis
<span class="sort-icons">
<i class="fa-solid fa-chevron-up"></i>
<i class="fa-solid fa-chevron-down"></i>
</span>
</th>
<th>
Satuan
<span class="sort-icons">
<i class="fa-solid fa-chevron-up"></i>
<i class="fa-solid fa-chevron-down"></i>
</span>
</th>
<th>
Pemakaian Terakhir
<span class="sort-icons">
<i class="fa-solid fa-chevron-up"></i>
<i class="fa-solid fa-chevron-down"></i>
</span>
</th>
<th>
Waktu Pemakaian Terakhir
<span class="sort-icons">
<i class="fa-solid fa-chevron-up"></i>
<i class="fa-solid fa-chevron-down"></i>
</span>
</th>
<th>
Prediksi Sekarang
<span class="sort-icons">
<i class="fa-solid fa-chevron-up"></i>
<i class="fa-solid fa-chevron-down"></i>
</span>
</th>
<th>
Selisih
<span class="sort-icons">
<i class="fa-solid fa-chevron-up"></i>
<i class="fa-solid fa-chevron-down"></i>
</span>
</th>
<th>
Status
<span class="sort-icons">
<i class="fa-solid fa-chevron-up"></i>
<i class="fa-solid fa-chevron-down"></i>
</span>
</th>
<th>
Waktu Prediksi
<span class="sort-icons">
<i class="fa-solid fa-chevron-up"></i>
<i class="fa-solid fa-chevron-down"></i>
</span>
</th>
<th>Aksi</th>
{{-- <th>Fluktuatif</th> --}}
{{-- <th>Rata-rata 3 Bulan</th> --}}
</tr>
</thead>
<tbody id="tableBody">
@forelse ($monitoringData as $row)
<tr>
<td class="sticky-col sticky-kode">
<span class="kode-badge">{{ $row->kode }}</span>
</td>
<td class="sticky-col sticky-nama sticky-shadow">
{{ $row->nama_barang }}
</td>
<td>{{ $row->jenis }}</td>
<td>{{ $row->satuan }}</td>
{{-- <td>
@if ($row->fluktuatif == 1)
<span class="badge yellow">Fluktuatif</span>
@else
<span class="badge green">Stabil</span>
@endif
</td> --}}
<td>{{ $row->pemakaian_terakhir ?? '-' }}</td>
<td>
@if ($row->bulan_terakhir && $row->tahun_terakhir)
{{ \Carbon\Carbon::create()->month($row->bulan_terakhir)->translatedFormat('F') }},
{{ $row->tahun_terakhir }}
@else
-
@endif
</td>
<td>{{ $row->jumlah_prediksi }}</td>
<td>
@if ($row->selisih > 0)
+{{ $row->selisih }}
@else
{{ $row->selisih }}
@endif
</td>
<td>
@if ($row->status == 'naik')
<span class="badge blue">Naik</span>
@elseif ($row->status == 'turun')
<span class="badge yellow">Turun</span>
@else
<span class="badge green">Stabil</span>
@endif
</td>
<td>
@if ($row->tanggal_prediksi)
{{ \Carbon\Carbon::parse($row->tanggal_prediksi)->translatedFormat('d F Y') }}
@else
-
@endif
</td>
<td>
<button class="btn btn-delete"
onclick="confirmDelete('{{ $row->kode }}')">
<i class="bi bi-trash"></i>
</button>
</td>
</tr>
@empty
<tr>
<td colspan="12" style="text-align:center;">
Tidak ada data monitoring
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="table-footer">
<div class="table-info" id="tableInfo">
Menampilkan 1 sampai {{ count($monitoringData) }} dari {{ count($monitoringData) }} data
</div>
<div class="pagination" id="pagination"></div>
</div>
</div>
{{-- <div class="card">
<div class="section-title">Monitoring Sistem & Sinkronisasi</div>
<div class="summary-grid">
<div class="summary-card">
<div class="title">Sinkronisasi Terakhir</div>
<div class="value" style="font-size:20px;">
17 Mei 2026
</div>
<div class="desc">08:14 WIB</div>
</div>
<div class="summary-card">
<div class="title">Data Berhasil Sinkron</div>
<div class="value">12.540</div>
<div class="desc">Detail transaksi</div>
</div>
<div class="summary-card">
<div class="title">Data Gagal Sinkron</div>
<div class="value">3</div>
<div class="desc">Perlu pengecekan</div>
</div>
<div class="summary-card">
<div class="title">Status Sistem</div>
<div class="value" style="font-size:22px;">
Aktif
</div>
<div class="desc">Semua layanan normal</div>
</div>
</div>
</div>
<div class="card">
<div class="section-title">Aktivitas Terakhir</div>
<div class="activity-item">
<strong>Prediksi berhasil dibuat</strong>
146 data prediksi berhasil diproses pada 17 Mei 2026 08:10 WIB
</div>
<div class="activity-item">
<strong>Sinkronisasi berhasil</strong>
Sistem berhasil mengambil data transaksi terbaru
</div>
<div class="activity-item">
<strong>Deteksi anomali</strong>
12 barang mengalami perubahan prediksi signifikan
</div>
</div> --}}
<div class="footer-red">
<div>
<strong>SIMDONDAR</strong> Sistem Monitoring Prediksi Barang Habis Pakai
</div>
<div>
UDD PMI Kabupaten Jember
</div>
</div>
</div>
</div>
<div id="deleteModal" class="modal-overlay">
<div class="modal-box">
<div class="modal-icon">
<i class="bi bi-exclamation-triangle"></i>
</div>
<h3>Konfirmasi Hapus</h3>
<p>Data monitoring ini akan dihapus permanen.</p>
<div class="modal-action">
<button class="btn-cancel" onclick="closeModal()">Batal</button>
<button class="btn-confirm" id="btnConfirmDelete">Hapus</button>
</div>
</div>
</div>
<script>
const availablePeriods = @json($availablePeriods);
function cekPeriode() {
const periode = document.getElementById('periode').value;
const btnFilter = document.getElementById('btnFilter');
const btnPrint = document.getElementById('btnPrint');
const note = document.getElementById('notePeriode');
if (!periode) {
btnFilter.disabled = true;
btnFilter.style.opacity = 0.5;
btnFilter.style.cursor = 'not-allowed';
btnPrint.disabled = true;
btnPrint.style.opacity = 0.5;
btnPrint.style.cursor = 'not-allowed';
note.style.display = 'none';
return;
}
if (availablePeriods.includes(periode)) {
btnFilter.disabled = false;
btnFilter.style.opacity = 1;
btnFilter.style.cursor = 'pointer';
btnPrint.disabled = false;
btnPrint.style.opacity = 1;
btnPrint.style.cursor = 'pointer';
note.style.display = 'none';
} else {
btnFilter.disabled = true;
btnFilter.style.opacity = 0.5;
btnFilter.style.cursor = 'not-allowed';
btnPrint.disabled = true;
btnPrint.style.opacity = 0.5;
btnPrint.style.cursor = 'not-allowed';
note.style.display = 'block';
}
}
document.addEventListener("DOMContentLoaded", function() {
cekPeriode();
window.filterApplied = true;
const periode = document.getElementById('periode').value;
document.getElementById('periodeText').innerText = formatPeriode(periode);
attachDonutTooltip();
const btn = document.getElementById("btnPrint");
btn.disabled = false;
btn.style.opacity = 1;
btn.style.cursor = "pointer";
});
document.getElementById('filterForm').addEventListener('submit', function(e) {
e.preventDefault();
submitFilter();
});
let searchTimeout = null;
document.querySelector('input[name="search"]').addEventListener('input', function() {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
submitFilter();
}, 400);
});
function formatPeriode(periode) {
if (!periode) return '';
const [tahun, bulan] = periode.split('-');
const namaBulan = [
'', 'Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni',
'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember'
];
return namaBulan[parseInt(bulan)] + ' ' + tahun;
}
let currentFetchController = null;
function submitFilter() {
if (currentFetchController) {
currentFetchController.abort();
}
currentFetchController = new AbortController();
window.filterApplied = true;
window.filterApplied = true;
cekPeriode();
const form = document.getElementById('filterForm');
const params = new URLSearchParams(new FormData(form)).toString();
const periode = document.getElementById('periode').value;
fetch("{{ route('monitoring-prediksi') }}?" + params, {
signal: currentFetchController.signal,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(res => res.text())
.then(html => {
const doc = new DOMParser().parseFromString(html, 'text/html');
const newTable = doc.querySelector('.table-wrapper');
document.querySelector('.table-wrapper').innerHTML = newTable.innerHTML;
const newDonut = doc.querySelector('.donut-wrapper');
document.querySelector('.donut-wrapper').innerHTML = newDonut.innerHTML;
const newSummary = doc.querySelector('#summaryWrapper');
document.querySelector('#summaryWrapper').innerHTML = newSummary.innerHTML;
doc.querySelectorAll("script").forEach(s => {
if (s.innerText.includes("window.donutRawData")) {
eval(s.innerText);
}
});
attachDonutTooltip();
initTableSorting();
initPagination();
document.getElementById("tooltipDonut").classList.remove("show");
document.getElementById('periodeText').innerText = formatPeriode(periode);
document.querySelector('.table-wrapper').scrollIntoView({
behavior: 'smooth'
});
})
.catch(err => {
if (err.name === 'AbortError') {
return;
}
console.error(err);
});
}
document.getElementById('btnPrint').addEventListener('click', function() {
const form = document.getElementById('filterForm');
const params = new URLSearchParams(new FormData(form)).toString();
window.location.href = "{{ route('monitoring.print') }}?" + params;
});
let deleteKode = null;
function confirmDelete(kode) {
deleteKode = kode;
document.getElementById('deleteModal').style.display = 'flex';
}
function closeModal() {
document.getElementById('deleteModal').style.display = 'none';
deleteKode = null;
}
document.getElementById('btnConfirmDelete').addEventListener('click', function() {
if (!deleteKode) return;
fetch(`/monitoring/delete/${deleteKode}`, {
method: 'DELETE',
headers: {
'X-CSRF-TOKEN': '{{ csrf_token() }}',
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(() => {
location.reload();
});
});
function initTableSorting() {
const table = document.querySelector("table");
if (!table) return;
const tbody = table.querySelector("tbody");
const headers = table.querySelectorAll("thead th");
headers.forEach((th, i) => {
const up = th.querySelector(".fa-chevron-up");
const down = th.querySelector(".fa-chevron-down");
if (!up || !down) return;
up.onclick = null;
down.onclick = null;
function getValue(row, index) {
return row.children[index].innerText.trim().toLowerCase();
}
function sortTable(asc = true) {
let rows = Array.from(tbody.querySelectorAll("tr"));
rows.sort((a, b) => {
let aVal = getValue(a, i);
let bVal = getValue(b, i);
let numA = parseFloat(aVal.replace(/[^0-9.-]/g, ''));
let numB = parseFloat(bVal.replace(/[^0-9.-]/g, ''));
if (!isNaN(numA) && !isNaN(numB)) {
return asc ? numA - numB : numB - numA;
}
return asc ?
aVal.localeCompare(bVal) :
bVal.localeCompare(aVal);
});
tbody.innerHTML = "";
rows.forEach(r => tbody.appendChild(r));
}
up.style.cursor = "pointer";
down.style.cursor = "pointer";
up.onclick = () => sortTable(true);
down.onclick = () => sortTable(false);
});
}
document.addEventListener("DOMContentLoaded", function() {
initTableSorting();
initPagination();
});
</script>
<script>
const tooltip = document.getElementById("tooltipDonut");
let currentPage = 1;
let rowsPerPage = 10;
function initPagination() {
const tbody = document.getElementById("tableBody");
if (!tbody) return;
const rows = Array.from(tbody.querySelectorAll("tr"));
if (!rows.length) return;
const pagination = document.getElementById("pagination");
const tableInfo = document.getElementById("tableInfo");
const totalRows = rows.length;
const totalPages = Math.ceil(totalRows / rowsPerPage);
const rowsSelect = document.getElementById("rowsPerPageSelect");
if (rowsSelect) {
rowsSelect.value = rowsPerPage;
rowsSelect.onchange = function() {
rowsPerPage = parseInt(this.value);
currentPage = 1;
initPagination();
};
}
function renderTable() {
const start = (currentPage - 1) * rowsPerPage;
const end = start + rowsPerPage;
rows.forEach((row, index) => {
row.style.display =
index >= start && index < end ?
"" :
"none";
});
const showingFrom = totalRows === 0 ? 0 : start + 1;
const showingTo = Math.min(end, totalRows);
tableInfo.innerText =
`Menampilkan ${showingFrom} sampai ${showingTo} dari ${totalRows} data`;
renderPagination();
}
function renderPagination() {
pagination.innerHTML = "";
const prevBtn = document.createElement("button");
prevBtn.innerHTML = `<i class="bi bi-chevron-left"></i>`;
prevBtn.disabled = currentPage === 1;
prevBtn.onclick = () => {
currentPage--;
renderTable();
};
pagination.appendChild(prevBtn);
function createPageButton(page) {
const btn = document.createElement("button");
btn.innerText = page;
if (page === currentPage) {
btn.classList.add("active");
}
btn.onclick = () => {
currentPage = page;
renderTable();
};
pagination.appendChild(btn);
}
function createDots() {
const dots = document.createElement("button");
dots.innerText = "...";
dots.disabled = true;
pagination.appendChild(dots);
}
if (totalPages <= 7) {
for (let i = 1; i <= totalPages; i++) {
createPageButton(i);
}
} else {
createPageButton(1);
if (currentPage > 4) {
createDots();
}
let startPage = Math.max(2, currentPage - 1);
let endPage = Math.min(totalPages - 1, currentPage + 1);
if (currentPage <= 4) {
startPage = 2;
endPage = 5;
}
if (currentPage >= totalPages - 3) {
startPage = totalPages - 4;
endPage = totalPages - 1;
}
for (let i = startPage; i <= endPage; i++) {
createPageButton(i);
}
if (currentPage < totalPages - 3) {
createDots();
}
createPageButton(totalPages);
}
const nextBtn = document.createElement("button");
nextBtn.innerHTML = `<i class="bi bi-chevron-right"></i>`;
nextBtn.disabled = currentPage === totalPages;
nextBtn.onclick = () => {
currentPage++;
renderTable();
};
pagination.appendChild(nextBtn);
}
renderTable();
}
function attachDonutTooltip() {
const tooltip = document.getElementById("tooltipDonut");
const donut = document.querySelector(".donut-svg");
if (!donut) return;
donut.querySelectorAll(".donut-slice").forEach(slice => {
slice.onmousemove = null;
slice.onmouseleave = null;
slice.addEventListener("mousemove", (e) => {
const label = slice.dataset.label;
const percent = parseFloat(slice.dataset.value) || 0;
const rawData = window.donutRawData || {};
const realValue = rawData[label] || 0;
tooltip.innerHTML = `
<div class="tooltip-row">
<span class="tooltip-color" style="background:${slice.getAttribute("stroke")}"></span>
<div>
<div class="tooltip-title">${label}</div>
<div class="tooltip-value">
${Math.round(percent)}% (${realValue} item)
</div>
</div>
</div>
`;
tooltip.style.left = e.clientX + "px";
tooltip.style.top = e.clientY + "px";
tooltip.classList.add("show");
});
slice.addEventListener("mouseleave", () => {
tooltip.classList.remove("show");
});
});
}
</script>
<script>
window.donutRawData = {
Naik: {{ $totalNaik }},
Stabil: {{ $totalStabil }},
Turun: {{ $totalTurun }}
};
</script>
</body>
</html>