first commit

This commit is contained in:
ryan
2026-02-06 14:22:35 +07:00
commit bd70440c71
185 changed files with 69518 additions and 0 deletions
@@ -0,0 +1,225 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Billing INACBG</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container-fluid">
<div class="row h-100">
<!-- Sidebar -->
<div class="col-md-3 sidebar">
<div class="sidebar-header">
<h5>Ruangan</h5>
</div>
<div class="ruangan-list" id="ruanganList">
<!-- Will be populated by JS -->
</div>
</div>
<!-- Main Content -->
<div class="col-md-9 main-content">
<!-- Header -->
<div class="header">
<div class="d-flex justify-content-between align-items-center">
<h2>Data Billing Pasien</h2>
<div class="text-muted small" id="currentDate"></div>
</div>
<div class="search-box mt-3">
<input type="text" id="searchInput" class="form-control" placeholder="Cari billing pasien dian">
</div>
</div>
<!-- Billing Table -->
<div class="billing-table-container">
<table class="table table-hover">
<thead>
<tr>
<th>ID Pasien</th>
<th>Nama</th>
<th>Total Tarif RS</th>
<th>Total Klaim BPJS</th>
<th>Billing Sign</th>
<th>Action</th>
</tr>
</thead>
<tbody id="billingTableBody">
<!-- Will be populated by JS -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Edit Modal -->
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editModalLabel">Data Pasien</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- Patient Info Section -->
<div class="mb-4">
<h6 class="text-secondary">Nama Lengkap</h6>
<input type="text" id="modalNamaPasien" class="form-control" readonly>
</div>
<div class="row mb-4">
<div class="col-md-6">
<h6 class="text-secondary">ID Pasien</h6>
<input type="text" id="modalIdPasien" class="form-control" readonly>
</div>
<div class="col-md-6">
<h6 class="text-secondary">Kelas</h6>
<input type="text" id="modalKelas" class="form-control" readonly>
</div>
</div>
<!-- Dokter yang Menangani -->
<div class="mb-4">
<h6 class="text-secondary fw-bold">Dokter yang Menangani</h6>
<div id="dokterList" class="border rounded p-2 bg-light small">
<span class="text-muted">Memuat data dokter...</span>
</div>
</div>
<!-- Tindakan & ICD - Pisah Lama vs Baru -->
<div class="mb-4">
<h6 class="text-secondary fw-bold">Tindakan RS</h6>
<div class="row">
<div class="col-md-6">
<small class="text-muted">Data yang sudah ada:</small>
<div id="tindakanLama" class="border rounded p-2 bg-light small"></div>
</div>
<div class="col-md-6">
<small class="text-muted">Data baru (akan ditambahkan):</small>
<div id="tindakanBaru" class="border rounded p-2 bg-light small text-muted">Belum ada data baru</div>
</div>
</div>
</div>
<div class="mb-4">
<h6 class="text-secondary fw-bold">ICD 9 & ICD 10</h6>
<div class="row">
<div class="col-md-6">
<small class="text-muted">ICD 9 - Data yang sudah ada:</small>
<div id="icd9Lama" class="border rounded p-2 bg-light small mb-2"></div>
<small class="text-muted">ICD 10 - Data yang sudah ada:</small>
<div id="icd10Lama" class="border rounded p-2 bg-light small"></div>
</div>
<div class="col-md-6">
<small class="text-muted">ICD 9 - Data baru:</small>
<div id="icd9Baru" class="border rounded p-2 bg-light small mb-2 text-muted">Belum ada data baru</div>
<small class="text-muted">ICD 10 - Data baru:</small>
<div id="icd10Baru" class="border rounded p-2 bg-light small text-muted">Belum ada data baru</div>
</div>
</div>
</div>
<div class="mb-4">
<h6 class="text-secondary">Total Tarif RS (Kumulatif)</h6>
<input type="text" id="modalTotalTarif" class="form-control" readonly>
</div>
<!-- INACBG Form -->
<div class="mb-4">
<h6 class="text-secondary">INA CBG</h6>
<form id="inacbgForm">
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">Tipe INACBG</label>
<select id="tipeInacbg" class="form-select">
<option value="">-- Pilih Tipe --</option>
<option value="RI">RI (Rawat Inap)</option>
<option value="RJ">RJ (Rawat Jalan)</option>
</select>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">Masukkan Kode INA CBGS</label>
<div class="input-group">
<select id="inacbgCode" class="form-select" disabled>
<option value="">-- Pilih Tipe INACBG Dulu --</option>
</select>
<span class="input-group-text" style="cursor: pointer; user-select: none;" title="Ganti ke input manual" onclick="toggleInacbgInput()">
↔️
</span>
</div>
<input type="text" id="inacbgCodeManual" class="form-control d-none mt-2" placeholder="Ketik kode INACBG manual">
</div>
<div class="col-md-6 d-flex align-items-end">
<button type="button" id="addCodeBtn" class="btn btn-primary w-100">+</button>
</div>
</div>
<!-- INACBG Lama -->
<div class="mb-3">
<label class="form-label fw-bold">INACBG yang sudah ada sebelumnya:</label>
<div id="inacbgLamaContainer" class="border rounded p-2 bg-light small">
<div id="inacbgRILama" class="mb-1"></div>
<div id="inacbgRJLama"></div>
<div id="totalKlaimLama" class="mt-2 fw-bold"></div>
</div>
</div>
<!-- INACBG Baru -->
<div class="mb-3">
<label class="form-label fw-bold">INACBG Baru (akan ditambahkan):</label>
<div id="codeList" class="border rounded p-2 bg-light">
<small class="text-muted">Belum ada kode baru</small>
</div>
</div>
<div class="row mb-3">
<div class="col-md-4">
<label class="form-label">Total Klaim Lama</label>
<input type="number" id="totalKlaimLamaInput" class="form-control" placeholder="0" readonly>
</div>
<div class="col-md-4">
<label class="form-label">Total Klaim Baru <span class="text-muted small">(Otomatis)</span></label>
<input type="number" id="totalKlaim" class="form-control" placeholder="0" readonly>
</div>
<div class="col-md-4">
<label class="form-label">Total Klaim Akhir</label>
<input type="number" id="totalKlaimAkhir" class="form-control fw-bold" placeholder="0" readonly>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">Billing Sign</label>
<div id="billingSignContainer" class="mt-1">
<span id="billingSignBadge" class="badge bg-secondary">-</span>
<span id="billingSignText" class="ms-2 text-muted small"></span>
</div>
</div>
<div class="col-md-6">
<label class="form-label">Tanggal Keluar</label>
<input type="date" id="tanggalKeluar" class="form-control">
</div>
</div>
<div id="formAlert" class="alert d-none" role="alert"></div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-success">Save</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="scriptAdmin.js"></script>
</body>
</html>
@@ -0,0 +1,158 @@
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Billing INACBG</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container-fluid">
<div class="row h-100">
<!-- Sidebar -->
<div class="col-md-3 sidebar">
<div class="sidebar-header">
<h5>Ruangan</h5>
</div>
<div class="ruangan-list" id="ruanganList">
<!-- Will be populated by JS -->
</div>
</div>
<!-- Main Content -->
<div class="col-md-9 main-content">
<!-- Header -->
<div class="header">
<div class="d-flex justify-content-between align-items-center">
<h2>Data Billing Pasien</h2>
<div class="text-muted small" id="currentDate"></div>
</div>
<div class="search-box mt-3">
<input type="text" id="searchInput" class="form-control" placeholder="Cari billing pasien dian">
</div>
</div>
<!-- Billing Table -->
<div class="billing-table-container">
<table class="table table-hover">
<thead>
<tr>
<th>ID Pasien</th>
<th>Nama</th>
<th>Billing Sign</th>
<th>Action</th>
</tr>
</thead>
<tbody id="billingTableBody">
<!-- Will be populated by JS -->
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Edit Modal -->
<div class="modal fade" id="editModal" tabindex="-1" aria-labelledby="editModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editModalLabel">Data Pasien</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- Patient Info Section -->
<div class="mb-4">
<h6 class="text-secondary">Nama Lengkap</h6>
<input type="text" id="modalNamaPasien" class="form-control" readonly>
</div>
<div class="row mb-4">
<div class="col-md-6">
<h6 class="text-secondary">ID Pasien</h6>
<input type="text" id="modalIdPasien" class="form-control" readonly>
</div>
<div class="col-md-6">
<h6 class="text-secondary">Kelas</h6>
<input type="text" id="modalKelas" class="form-control" readonly>
</div>
</div>
<!-- Tindakan & Pemeriksaan -->
<div class="mb-4">
<h6 class="text-secondary">Tindakan dan Pemeriksaan Penunjang</h6>
<input type="text" id="modalTindakan" class="form-control" readonly placeholder="(diambil dari billing)">
</div>
<div class="mb-4">
<h6 class="text-secondary">Total Tarif RS</h6>
<input type="text" id="modalTotalTarif" class="form-control" readonly>
</div>
<!-- ICD Codes -->
<div class="row mb-4">
<div class="col-md-6">
<h6 class="text-secondary">ICD 9</h6>
<input type="text" id="modalICD9" class="form-control" readonly>
</div>
<div class="col-md-6">
<h6 class="text-secondary">ICD 10</h6>
<input type="text" id="modalICD10" class="form-control" readonly>
</div>
</div>
<!-- INACBG Form -->
<div class="mb-4">
<h6 class="text-secondary">INA CBG</h6>
<form id="inacbgForm">
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">Tipe INACBG</label>
<select id="tipeInacbg" class="form-select">
<option value="">-- Pilih Tipe --</option>
<option value="RI">RI (Rawat Inap)</option>
<option value="RJ">RJ (Rawat Jalan)</option>
</select>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">Masukkan Kode INA CBGS</label>
<select id="inacbgCode" class="form-select" disabled>
<option value="">-- Pilih Tipe INACBG Dulu --</option>
</select>
</div>
<div class="col-md-6 d-flex align-items-end">
<button type="button" id="addCodeBtn" class="btn btn-primary">+</button>
</div>
</div>
<div id="codeList" class="mb-3">
<!-- Added codes will appear here -->
</div>
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label">Total Klaim BJPS <span class="text-muted small">(Otomatis)</span></label>
<input type="number" id="totalKlaim" class="form-control" placeholder="0" readonly>
</div>
</div>
<div id="formAlert" class="alert d-none" role="alert"></div>
<div class="d-grid gap-2">
<button type="submit" class="btn btn-success">Save</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="scriptAdmin.js"></script>
</body>
</html>
@@ -0,0 +1,715 @@
// Configuration
const API_BASE = 'http://localhost:8081';
let billingData = [];
let currentEditingBilling = null;
let inacbgCodes = [];
let tarifCache = {}; // Cache for tarif data
let isManualInacbgMode = false; // Track if user is in manual input mode
// Initialize on page load
document.addEventListener('DOMContentLoaded', () => {
updateCurrentDate();
loadBillingData();
setupEventListeners();
});
// Update current date
function updateCurrentDate() {
const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
const today = new Date().toLocaleDateString('id-ID', options);
document.getElementById('currentDate').textContent = today;
}
// Load billing data from API
async function loadBillingData() {
try {
const res = await fetch(`${API_BASE}/admin/billing`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
billingData = data.data || [];
console.log('Billing data loaded:', billingData);
// Debug: cek apakah total_klaim ada di response
if (billingData.length > 0) {
console.log('Sample billing item:', billingData[0]);
console.log('Total klaim dari sample:', billingData[0].total_klaim);
}
renderBillingTable();
renderRuanganSidebar();
} catch (err) {
console.error('Error loading billing data:', err);
document.getElementById('billingTableBody').innerHTML = `
<tr>
<td colspan="4" class="text-center text-danger">Gagal memuat data: ${err.message}</td>
</tr>
`;
}
}
// Render billing table
function renderBillingTable() {
const tbody = document.getElementById('billingTableBody');
tbody.innerHTML = '';
if (billingData.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="6" class="text-center text-muted">Tidak ada data billing</td>
</tr>
`;
return;
}
billingData.forEach(billing => {
const row = document.createElement('tr');
const badgeClass = getBillingSignBadgeClass(billing.billing_sign);
const badgeColor = getBillingSignColor(billing.billing_sign);
const totalTarif = billing.total_tarif_rs || 0;
const totalKlaim = billing.total_klaim || 0;
row.innerHTML = `
<td>${billing.id_pasien || '-'}</td>
<td>
<a href="#" class="text-primary text-decoration-none" onclick="openEditModal(${billing.id_billing}); return false;">
${billing.nama_pasien || '-'}
</a>
</td>
<td>Rp ${Number(totalTarif).toLocaleString('id-ID')}</td>
<td>Rp ${Number(totalKlaim).toLocaleString('id-ID')}</td>
<td>
<span class="billing-sign-badge ${badgeClass}" style="background-color: ${badgeColor};" title="${billing.billing_sign}"></span>
</td>
<td>
<button class="btn btn-sm btn-primary" onclick="openEditModal(${billing.id_billing})">
✎ Edit
</button>
</td>
`;
tbody.appendChild(row);
});
}
// Get billing sign badge class and color
function getBillingSignColor(billingSign) {
const normalizedSign = (billingSign || '').toString().toLowerCase();
switch (normalizedSign) {
case 'hijau':
return '#28a745';
case 'kuning':
return '#ffc107';
case 'orange':
return '#fd7e14';
case 'merah':
case 'created':
return '#dc3545';
default:
return '#6c757d';
}
}
function getBillingSignBadgeClass(billingSign) {
const normalizedSign = (billingSign || '').toString().toLowerCase();
switch (normalizedSign) {
case 'hijau':
return 'hijau';
case 'kuning':
return 'kuning';
case 'orange':
return 'orange';
case 'merah':
return 'merah';
case 'created':
return 'created';
default:
return 'created';
}
}
// Render ruangan sidebar
function renderRuanganSidebar() {
const uniqueRuangans = [...new Set(billingData.map(b => b.ruangan))];
const ruanganList = document.getElementById('ruanganList');
ruanganList.innerHTML = '';
if (uniqueRuangans.length === 0) {
ruanganList.innerHTML = '<p class="text-muted">Tidak ada ruangan</p>';
return;
}
uniqueRuangans.forEach((ruangan, index) => {
const item = document.createElement('div');
item.className = 'ruangan-item';
item.textContent = ruangan || `Ruangan ${index + 1}`;
item.onclick = () => filterByRuangan(ruangan);
ruanganList.appendChild(item);
});
}
// Filter billing by ruangan
function filterByRuangan(ruangan) {
const filtered = billingData.filter(b => b.ruangan === ruangan);
const tbody = document.getElementById('billingTableBody');
tbody.innerHTML = '';
if (filtered.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="6" class="text-center text-muted">Tidak ada data untuk ruangan ini</td>
</tr>
`;
return;
}
filtered.forEach(billing => {
const row = document.createElement('tr');
const badgeColor = getBillingSignColor(billing.billing_sign);
const badgeClass = getBillingSignBadgeClass(billing.billing_sign);
const totalTarif = billing.total_tarif_rs || 0;
const totalKlaim = billing.total_klaim || 0;
row.innerHTML = `
<td>${billing.id_pasien || '-'}</td>
<td>
<a href="#" class="text-primary text-decoration-none" onclick="openEditModal(${billing.id_billing}); return false;">
${billing.nama_pasien || '-'}
</a>
</td>
<td>Rp ${Number(totalTarif).toLocaleString('id-ID')}</td>
<td>Rp ${Number(totalKlaim).toLocaleString('id-ID')}</td>
<td>
<span class="billing-sign-badge ${badgeClass}" style="background-color: ${badgeColor};" title="${billing.billing_sign}"></span>
</td>
<td>
<button class="btn btn-sm btn-primary" onclick="openEditModal(${billing.id_billing})">
✎ Edit
</button>
</td>
`;
tbody.appendChild(row);
});
}
// Open edit modal
function openEditModal(billingId) {
currentEditingBilling = billingData.find(b => b.id_billing === billingId);
if (!currentEditingBilling) {
alert('Data billing tidak ditemukan');
return;
}
// Populate modal with billing data
document.getElementById('modalNamaPasien').value = currentEditingBilling.nama_pasien || '';
document.getElementById('modalIdPasien').value = currentEditingBilling.id_pasien || '';
document.getElementById('modalKelas').value = currentEditingBilling.Kelas || '';
// Tampilkan dokter yang menangani pasien
const dokterList = currentEditingBilling.nama_dokter || [];
const dokterListEl = document.getElementById('dokterList');
if (dokterList.length > 0) {
dokterListEl.innerHTML = dokterList.map(dokter =>
`<span class="badge bg-info me-2 mb-1">${dokter}</span>`
).join('');
} else {
dokterListEl.innerHTML = '<span class="text-muted">Belum ada data dokter</span>';
}
// Total tarif & total klaim kumulatif
// Handle berbagai kemungkinan nama field (case-insensitive)
const totalTarif = Number(currentEditingBilling.total_tarif_rs || currentEditingBilling.Total_Tarif_RS || 0);
const totalKlaimLama = Number(currentEditingBilling.total_klaim || currentEditingBilling.Total_Klaim || currentEditingBilling.total_klaim_lama || 0);
document.getElementById('modalTotalTarif').value = totalTarif.toLocaleString('id-ID');
// Tindakan RS - semua yang ada sekarang = "lama" (karena tidak ada cara membedakan mana yang baru)
const tindakanLama = currentEditingBilling.tindakan_rs || [];
document.getElementById('tindakanLama').textContent = tindakanLama.length > 0 ? tindakanLama.join(', ') : 'Tidak ada';
document.getElementById('tindakanBaru').textContent = 'Belum ada data baru';
// ICD9 & ICD10 - semua yang ada sekarang = "lama"
const icd9Lama = currentEditingBilling.icd9 || [];
const icd10Lama = currentEditingBilling.icd10 || [];
document.getElementById('icd9Lama').textContent = icd9Lama.length > 0 ? icd9Lama.join(', ') : 'Tidak ada';
document.getElementById('icd10Lama').textContent = icd10Lama.length > 0 ? icd10Lama.join(', ') : 'Tidak ada';
document.getElementById('icd9Baru').textContent = 'Belum ada data baru';
document.getElementById('icd10Baru').textContent = 'Belum ada data baru';
// INACBG Lama
const existingRI = currentEditingBilling.inacbg_ri || [];
const existingRJ = currentEditingBilling.inacbg_rj || [];
const inacbgRILamaEl = document.getElementById('inacbgRILama');
const inacbgRJLamaEl = document.getElementById('inacbgRJLama');
const totalKlaimLamaEl = document.getElementById('totalKlaimLama');
// Debug: log untuk cek data yang diterima
console.log('=== DEBUG TOTAL KLAIM LAMA ===');
console.log('Current editing billing:', currentEditingBilling);
console.log('total_klaim:', currentEditingBilling.total_klaim);
console.log('Total_Klaim:', currentEditingBilling.Total_Klaim);
console.log('total_klaim_lama:', currentEditingBilling.total_klaim_lama);
console.log('Total klaim lama (processed):', totalKlaimLama);
console.log('All keys in billing object:', Object.keys(currentEditingBilling));
console.log('================================');
if (existingRI.length > 0) {
inacbgRILamaEl.innerHTML = `<strong>RI:</strong> ${existingRI.join(', ')}`;
} else {
inacbgRILamaEl.textContent = 'RI: Tidak ada';
}
if (existingRJ.length > 0) {
inacbgRJLamaEl.innerHTML = `<strong>RJ:</strong> ${existingRJ.join(', ')}`;
} else {
inacbgRJLamaEl.textContent = 'RJ: Tidak ada';
}
// Tampilkan total klaim lama (selalu tampilkan, meskipun 0)
totalKlaimLamaEl.textContent = `Total Klaim Lama: Rp ${totalKlaimLama.toLocaleString('id-ID')}`;
// Set total klaim lama di input
document.getElementById('totalKlaimLamaInput').value = totalKlaimLama.toFixed(0);
// Set tanggal keluar jika ada
// (akan diisi oleh admin, jadi kosong dulu)
document.getElementById('tanggalKeluar').value = '';
// Reset INACBG form
inacbgCodes = [];
isManualInacbgMode = false;
document.getElementById('inacbgCode').value = '';
document.getElementById('inacbgCode').disabled = true;
document.getElementById('inacbgCode').classList.remove('d-none');
document.getElementById('inacbgCodeManual').value = '';
document.getElementById('inacbgCodeManual').classList.add('d-none');
document.getElementById('inacbgCode').innerHTML = '<option value="">-- Pilih Tipe INACBG Dulu --</option>';
document.getElementById('tipeInacbg').value = '';
document.getElementById('totalKlaim').value = '0';
document.getElementById('codeList').innerHTML = '<small class="text-muted">Belum ada kode baru</small>';
document.getElementById('totalKlaimAkhir').value = totalKlaimLama.toFixed(0);
document.getElementById('formAlert').classList.add('d-none');
// Update billing sign display awal
updateBillingSignDisplay();
// Show modal
const modal = new bootstrap.Modal(document.getElementById('editModal'));
modal.show();
}
// Toggle between dropdown and manual input
function toggleInacbgInput() {
isManualInacbgMode = !isManualInacbgMode;
const codeSelect = document.getElementById('inacbgCode');
const codeManual = document.getElementById('inacbgCodeManual');
if (isManualInacbgMode) {
// Switch to manual input
codeSelect.classList.add('d-none');
codeManual.classList.remove('d-none');
codeManual.focus();
codeManual.value = '';
} else {
// Switch back to dropdown
codeSelect.classList.remove('d-none');
codeManual.classList.add('d-none');
codeManual.value = '';
}
}
// Setup event listeners
function setupEventListeners() {
// Tipe INACBG change
document.getElementById('tipeInacbg').addEventListener('change', loadInacbgCodes);
// Add code button
document.getElementById('addCodeBtn').addEventListener('click', addInacbgCode);
// INACBG form submit
document.getElementById('inacbgForm').addEventListener('submit', submitInacbgForm);
// Search input
document.getElementById('searchInput').addEventListener('input', searchBilling);
// Manual input enter key
document.getElementById('inacbgCodeManual').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
addInacbgCode();
}
});
}
// Load INACBG codes based on tipe
async function loadInacbgCodes() {
const tipe = document.getElementById('tipeInacbg').value;
const codeSelect = document.getElementById('inacbgCode');
if (!tipe) {
codeSelect.disabled = true;
codeSelect.innerHTML = '<option value="">-- Pilih Tipe INACBG Dulu --</option>';
return;
}
const endpoint = tipe === 'RI' ? '/tarifBPJSRawatInap' : '/tarifBPJSRawatJalan';
try {
codeSelect.disabled = true;
codeSelect.innerHTML = '<option value="">Memuat...</option>';
// Check cache first
if (!tarifCache[tipe]) {
const res = await fetch(`${API_BASE}${endpoint}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
tarifCache[tipe] = await res.json();
}
const data = tarifCache[tipe] || [];
const items = Array.isArray(data) ? data : [];
codeSelect.innerHTML = '<option value="">-- Pilih Kode --</option>';
codeSelect.disabled = false;
items.forEach(item => {
const option = document.createElement('option');
// Use KodeINA as value and Deskripsi as display text
option.value = item.KodeINA || item.kodeINA || item.KodeINA || '';
option.textContent = item.Deskripsi || item.deskripsi || item.Deskripsi || '';
// If value is empty but we have other fields, try alternatives
if (!option.value) {
option.value = item.KodeINA_RJ || item.kodeINA_RJ || item.KodeINA_RI || item.kodeINA_RI || '';
}
codeSelect.appendChild(option);
});
console.log(`Loaded ${items.length} INACBG codes for type ${tipe}`);
} catch (err) {
console.error('Error loading INACBG codes:', err);
codeSelect.disabled = true;
codeSelect.innerHTML = `<option value="">Error: ${err.message}</option>`;
}
}
// Get tarif for a code from cache or return 0
function getTarifForCode(code, tipe, kelas = null) {
let tarif = 0;
const tarifData = tarifCache[tipe] || [];
const tarifItem = tarifData.find(item => (item.KodeINA || item.kodeINA) === code);
if (tarifItem) {
if (tipe === 'RI') {
// Get tarif based on patient class
if (!kelas) kelas = currentEditingBilling.Kelas;
if (kelas === '1') {
tarif = tarifItem.Kelas1 || 0;
} else if (kelas === '2') {
tarif = tarifItem.Kelas2 || 0;
} else if (kelas === '3') {
tarif = tarifItem.Kelas3 || 0;
}
} else if (tipe === 'RJ') {
// Get tarif directly from TarifINACBG field
tarif = tarifItem.TarifINACBG || tarifItem.tarif_inacbg || 0;
}
}
return tarif;
}
// Add INACBG code (from dropdown or manual input)
async function addInacbgCode() {
const tipe = document.getElementById('tipeInacbg').value;
if (!tipe) {
alert('Pilih tipe INACBG terlebih dahulu');
return;
}
let code = '';
let codeText = '';
if (isManualInacbgMode) {
// Manual input mode
const manualInput = document.getElementById('inacbgCodeManual').value.trim().toUpperCase();
if (!manualInput) {
alert('Masukkan kode INACBG');
return;
}
code = manualInput;
codeText = manualInput; // Manual input, use code as text
} else {
// Dropdown mode
const codeSelect = document.getElementById('inacbgCode');
const selectedOption = codeSelect.options[codeSelect.selectedIndex];
code = codeSelect.value.trim();
codeText = selectedOption.textContent.trim();
if (!code) {
alert('Pilih kode INACBG terlebih dahulu');
return;
}
}
if (inacbgCodes.some(c => c.value === code)) {
alert('Kode sudah ditambahkan');
return;
}
// Get tarif for this code
const tarif = getTarifForCode(code, tipe);
inacbgCodes.push({ value: code, text: codeText, tarif: tarif });
// Clear input/select
if (isManualInacbgMode) {
document.getElementById('inacbgCodeManual').value = '';
} else {
document.getElementById('inacbgCode').value = '';
}
renderCodeList();
calculateTotalKlaim(); // Update total after adding code
}
// Render code list
function renderCodeList() {
const codeList = document.getElementById('codeList');
codeList.innerHTML = '';
if (inacbgCodes.length === 0) {
codeList.innerHTML = '<small class="text-muted">Belum ada kode baru</small>';
return;
}
inacbgCodes.forEach((codeObj, index) => {
const badge = document.createElement('span');
badge.className = 'code-badge';
const tarifDisplay = codeObj.tarif ? `(Rp${codeObj.tarif.toLocaleString('id-ID')})` : '';
badge.innerHTML = `
${codeObj.text || codeObj.value} ${tarifDisplay}
<span class="remove-btn" onclick="removeInacbgCode(${index})">×</span>
`;
codeList.appendChild(badge);
});
}
// Calculate total klaim dari kode baru SAJA (lama sudah tercatat di total_klaim backend)
function calculateTotalKlaim() {
const totalBaru = inacbgCodes.reduce((sum, code) => sum + (code.tarif || 0), 0);
document.getElementById('totalKlaim').value = totalBaru.toFixed(0);
// Hitung total klaim akhir = lama + baru
const totalKlaimLama = parseFloat(document.getElementById('totalKlaimLamaInput').value) || 0;
const totalKlaimAkhir = totalKlaimLama + totalBaru;
document.getElementById('totalKlaimAkhir').value = totalKlaimAkhir.toFixed(0);
// Update billing sign display berdasarkan total tarif RS kumulatif vs total klaim akhir
updateBillingSignDisplay();
}
// Remove INACBG code
function removeInacbgCode(index) {
inacbgCodes.splice(index, 1);
renderCodeList();
calculateTotalKlaim(); // Update total after removing code
}
// Hitung billing sign berdasarkan rumus:
// persentase = (total_tarif_rs / total_klaim_akhir) * 100
function calculateBillingSign() {
// totalTarifRs sudah kumulatif (lama + baru) dari backend
const totalTarifRsStr = document.getElementById('modalTotalTarif').value.replace(/[^\d]/g, '');
const totalTarifRs = parseFloat(totalTarifRsStr) || 0;
// total klaim akhir = lama + baru
const totalKlaimAkhir = parseFloat(document.getElementById('totalKlaimAkhir').value) || 0;
if (totalTarifRs <= 0 || totalKlaimAkhir <= 0) {
return { sign: null, percentage: 0 };
}
const percentage = (totalTarifRs / totalKlaimAkhir) * 100;
let sign = 'hijau';
if (percentage <= 25) {
sign = 'hijau';
} else if (percentage >= 26 && percentage <= 50) {
sign = 'kuning';
} else if (percentage >= 51 && percentage <= 75) {
sign = 'orange';
} else if (percentage >= 76) {
sign = 'merah';
}
return { sign, percentage };
}
// Update tampilan billing sign di modal
function updateBillingSignDisplay() {
const container = document.getElementById('billingSignContainer');
const badgeEl = document.getElementById('billingSignBadge');
const textEl = document.getElementById('billingSignText');
if (!container || !badgeEl || !textEl) return;
const { sign, percentage } = calculateBillingSign();
if (!sign) {
badgeEl.className = 'badge bg-secondary';
badgeEl.textContent = '-';
textEl.textContent = '';
return;
}
const color = getBillingSignColor(sign);
badgeEl.className = 'badge';
badgeEl.style.backgroundColor = color;
badgeEl.textContent = sign.toUpperCase();
const roundedPct = percentage.toFixed(2);
textEl.textContent = `Tarif RS ≈ ${roundedPct}% dari BPJS`;
}
// Format billing sign ke Title Case agar sesuai enum di DB
function formatBillingSignValue(sign) {
if (!sign) return '';
const lower = sign.toLowerCase();
return lower.charAt(0).toUpperCase() + lower.slice(1);
}
// Submit INACBG form
async function submitInacbgForm(e) {
e.preventDefault();
const tipeInacbg = document.getElementById('tipeInacbg').value.trim();
// total klaim BARU (tambahan); lama sudah tersimpan di backend
const totalKlaimBaru = parseFloat(document.getElementById('totalKlaim').value) || 0;
// Validation
if (!currentEditingBilling) {
showAlert('danger', 'Data billing tidak ditemukan');
return;
}
if (inacbgCodes.length === 0) {
showAlert('danger', 'Tambahkan minimal satu kode INACBG');
return;
}
if (!tipeInacbg) {
showAlert('danger', 'Pilih tipe INACBG');
return;
}
if (totalKlaimBaru === 0) {
showAlert('danger', 'Total klaim tambahan tidak boleh 0');
return;
}
// Hitung billing sign berdasarkan total tarif RS dan total klaim
const { sign: billingSign } = calculateBillingSign();
const formattedBillingSign = formatBillingSignValue(billingSign);
// Ambil tanggal keluar jika diisi
const tanggalKeluar = document.getElementById('tanggalKeluar').value.trim();
// Prepare payload
const payload = {
id_billing: currentEditingBilling.id_billing,
tipe_inacbg: tipeInacbg,
kode_inacbg: inacbgCodes.map(c => c.value), // Extract just the codes
total_klaim: totalKlaimBaru, // Total klaim BARU saja (akan ditambahkan ke yang lama di backend)
billing_sign: formattedBillingSign, // kirim billing sign sesuai enum DB
tanggal_keluar: tanggalKeluar || '' // Tanggal keluar diisi oleh admin
};
try {
const res = await fetch(`${API_BASE}/admin/inacbg`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const result = await res.json();
if (!res.ok) {
throw new Error(result.error || result.message || 'Gagal menyimpan INACBG');
}
showAlert('success', 'INACBG berhasil disimpan');
setTimeout(() => {
bootstrap.Modal.getInstance(document.getElementById('editModal')).hide();
loadBillingData();
}, 1500);
} catch (err) {
console.error('Error:', err);
showAlert('danger', err.message);
}
}
// Show alert in modal
function showAlert(type, message) {
const alert = document.getElementById('formAlert');
alert.className = `alert alert-${type}`;
alert.textContent = message;
alert.classList.remove('d-none');
}
// Search billing
function searchBilling(e) {
const keyword = e.target.value.toLowerCase().trim();
if (keyword === '') {
renderBillingTable();
return;
}
const filtered = billingData.filter(b =>
(b.nama_pasien && b.nama_pasien.toLowerCase().includes(keyword)) ||
(b.id_pasien && b.id_pasien.toString().includes(keyword))
);
const tbody = document.getElementById('billingTableBody');
tbody.innerHTML = '';
if (filtered.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="6" class="text-center text-muted">Tidak ada hasil pencarian</td>
</tr>
`;
return;
}
filtered.forEach(billing => {
const row = document.createElement('tr');
const badgeColor = getBillingSignColor(billing.billing_sign);
const badgeClass = getBillingSignBadgeClass(billing.billing_sign);
const totalTarif = billing.total_tarif_rs || 0;
const totalKlaim = billing.total_klaim || 0;
row.innerHTML = `
<td>${billing.id_pasien || '-'}</td>
<td>
<a href="#" class="text-primary text-decoration-none" onclick="openEditModal(${billing.id_billing}); return false;">
${billing.nama_pasien || '-'}
</a>
</td>
<td>Rp ${Number(totalTarif).toLocaleString('id-ID')}</td>
<td>Rp ${Number(totalKlaim).toLocaleString('id-ID')}</td>
<td>
<span class="billing-sign-badge ${badgeClass}" style="background-color: ${badgeColor};" title="${billing.billing_sign}"></span>
</td>
<td>
<button class="btn btn-sm btn-primary" onclick="openEditModal(${billing.id_billing})">
✎ Edit
</button>
</td>
`;
tbody.appendChild(row);
});
}
@@ -0,0 +1,472 @@
// Configuration
const API_BASE = 'http://localhost:8081';
let billingData = [];
let currentEditingBilling = null;
let inacbgCodes = [];
let tarifCache = {}; // Cache for tarif data
// Initialize on page load
document.addEventListener('DOMContentLoaded', () => {
updateCurrentDate();
loadBillingData();
setupEventListeners();
});
// Update current date
function updateCurrentDate() {
const options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' };
const today = new Date().toLocaleDateString('id-ID', options);
document.getElementById('currentDate').textContent = today;
}
// Load billing data from API
async function loadBillingData() {
try {
const res = await fetch(`${API_BASE}/admin/billing`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
billingData = data.data || [];
console.log('Billing data loaded:', billingData);
renderBillingTable();
renderRuanganSidebar();
} catch (err) {
console.error('Error loading billing data:', err);
document.getElementById('billingTableBody').innerHTML = `
<tr>
<td colspan="4" class="text-center text-danger">Gagal memuat data: ${err.message}</td>
</tr>
`;
}
}
// Render billing table
function renderBillingTable() {
const tbody = document.getElementById('billingTableBody');
tbody.innerHTML = '';
if (billingData.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="4" class="text-center text-muted">Tidak ada data billing</td>
</tr>
`;
return;
}
billingData.forEach(billing => {
const row = document.createElement('tr');
const badgeClass = getBillingSignBadgeClass(billing.billing_sign);
const badgeColor = getBillingSignColor(billing.billing_sign);
row.innerHTML = `
<td>${billing.id_pasien || '-'}</td>
<td>
<a href="#" class="text-primary text-decoration-none" onclick="openEditModal(${billing.id_billing}); return false;">
${billing.nama_pasien || '-'}
</a>
</td>
<td>
<span class="billing-sign-badge ${badgeClass}" style="background-color: ${badgeColor};" title="${billing.billing_sign}"></span>
</td>
<td>
<button class="btn btn-sm btn-primary" onclick="openEditModal(${billing.id_billing})">
✎ Edit
</button>
</td>
`;
tbody.appendChild(row);
});
}
// Get billing sign badge class and color
function getBillingSignColor(billingSign) {
switch (billingSign) {
case 'hijau':
return '#28a745';
case 'kuning':
return '#ffc107';
case 'merah':
case 'created':
return '#dc3545';
default:
return '#6c757d';
}
}
function getBillingSignBadgeClass(billingSign) {
switch (billingSign) {
case 'hijau':
return 'hijau';
case 'kuning':
return 'kuning';
case 'merah':
return 'merah';
case 'created':
return 'created';
default:
return 'created';
}
}
// Render ruangan sidebar
function renderRuanganSidebar() {
const uniqueRuangans = [...new Set(billingData.map(b => b.ruangan))];
const ruanganList = document.getElementById('ruanganList');
ruanganList.innerHTML = '';
if (uniqueRuangans.length === 0) {
ruanganList.innerHTML = '<p class="text-muted">Tidak ada ruangan</p>';
return;
}
uniqueRuangans.forEach((ruangan, index) => {
const item = document.createElement('div');
item.className = 'ruangan-item';
item.textContent = ruangan || `Ruangan ${index + 1}`;
item.onclick = () => filterByRuangan(ruangan);
ruanganList.appendChild(item);
});
}
// Filter billing by ruangan
function filterByRuangan(ruangan) {
const filtered = billingData.filter(b => b.ruangan === ruangan);
const tbody = document.getElementById('billingTableBody');
tbody.innerHTML = '';
if (filtered.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="4" class="text-center text-muted">Tidak ada data untuk ruangan ini</td>
</tr>
`;
return;
}
filtered.forEach(billing => {
const row = document.createElement('tr');
const badgeColor = getBillingSignColor(billing.billing_sign);
const badgeClass = getBillingSignBadgeClass(billing.billing_sign);
row.innerHTML = `
<td>${billing.id_pasien || '-'}</td>
<td>
<a href="#" class="text-primary text-decoration-none" onclick="openEditModal(${billing.id_billing}); return false;">
${billing.nama_pasien || '-'}
</a>
</td>
<td>
<span class="billing-sign-badge ${badgeClass}" style="background-color: ${badgeColor};" title="${billing.billing_sign}"></span>
</td>
<td>
<button class="btn btn-sm btn-primary" onclick="openEditModal(${billing.id_billing})">
✎ Edit
</button>
</td>
`;
tbody.appendChild(row);
});
}
// Open edit modal
function openEditModal(billingId) {
currentEditingBilling = billingData.find(b => b.id_billing === billingId);
if (!currentEditingBilling) {
alert('Data billing tidak ditemukan');
return;
}
// Populate modal with billing data
document.getElementById('modalNamaPasien').value = currentEditingBilling.nama_pasien || '';
document.getElementById('modalIdPasien').value = currentEditingBilling.id_pasien || '';
document.getElementById('modalKelas').value = currentEditingBilling.Kelas || '';
document.getElementById('modalTindakan').value = (currentEditingBilling.tindakan_rs || []).join(', ') || '';
document.getElementById('modalTotalTarif').value = currentEditingBilling.total_tarif_rs || '';
document.getElementById('modalICD9').value = (currentEditingBilling.icd9 || []).join(', ') || '';
document.getElementById('modalICD10').value = (currentEditingBilling.icd10 || []).join(', ') || '';
// Reset INACBG form
inacbgCodes = [];
document.getElementById('inacbgCode').value = '';
document.getElementById('inacbgCode').disabled = true;
document.getElementById('inacbgCode').innerHTML = '<option value="">-- Pilih Tipe INACBG Dulu --</option>';
document.getElementById('tipeInacbg').value = '';
document.getElementById('totalKlaim').value = '';
document.getElementById('codeList').innerHTML = '';
document.getElementById('formAlert').classList.add('d-none');
// Show modal
const modal = new bootstrap.Modal(document.getElementById('editModal'));
modal.show();
}
// Setup event listeners
function setupEventListeners() {
// Tipe INACBG change
document.getElementById('tipeInacbg').addEventListener('change', loadInacbgCodes);
// Add code button
document.getElementById('addCodeBtn').addEventListener('click', addInacbgCode);
// INACBG form submit
document.getElementById('inacbgForm').addEventListener('submit', submitInacbgForm);
// Search input
document.getElementById('searchInput').addEventListener('input', searchBilling);
}
// Load INACBG codes based on tipe
async function loadInacbgCodes() {
const tipe = document.getElementById('tipeInacbg').value;
const codeSelect = document.getElementById('inacbgCode');
if (!tipe) {
codeSelect.disabled = true;
codeSelect.innerHTML = '<option value="">-- Pilih Tipe INACBG Dulu --</option>';
return;
}
const endpoint = tipe === 'RI' ? '/tarifBPJSRawatInap' : '/tarifBPJSRawatJalan';
try {
codeSelect.disabled = true;
codeSelect.innerHTML = '<option value="">Memuat...</option>';
// Check cache first
if (!tarifCache[tipe]) {
const res = await fetch(`${API_BASE}${endpoint}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
tarifCache[tipe] = await res.json();
}
const data = tarifCache[tipe] || [];
const items = Array.isArray(data) ? data : [];
codeSelect.innerHTML = '<option value="">-- Pilih Kode --</option>';
codeSelect.disabled = false;
items.forEach(item => {
const option = document.createElement('option');
// Use KodeINA as value and Deskripsi as display text
option.value = item.KodeINA || item.kodeINA || item.KodeINA || '';
option.textContent = item.Deskripsi || item.deskripsi || item.Deskripsi || '';
// If value is empty but we have other fields, try alternatives
if (!option.value) {
option.value = item.KodeINA_RJ || item.kodeINA_RJ || item.KodeINA_RI || item.kodeINA_RI || '';
}
codeSelect.appendChild(option);
});
console.log(`Loaded ${items.length} INACBG codes for type ${tipe}`);
} catch (err) {
console.error('Error loading INACBG codes:', err);
codeSelect.disabled = true;
codeSelect.innerHTML = `<option value="">Error: ${err.message}</option>`;
}
}
// Add INACBG code
async function addInacbgCode() {
const codeSelect = document.getElementById('inacbgCode');
const selectedOption = codeSelect.options[codeSelect.selectedIndex];
const code = codeSelect.value.trim();
const codeText = selectedOption.textContent.trim();
const tipe = document.getElementById('tipeInacbg').value;
if (!code) {
alert('Pilih kode INACBG terlebih dahulu');
return;
}
if (inacbgCodes.some(c => c.value === code)) {
alert('Kode sudah ditambahkan');
return;
}
// Get tarif for this code
let tarif = 0;
const tarifData = tarifCache[tipe] || [];
const tarifItem = tarifData.find(item => (item.KodeINA || item.kodeINA) === code);
if (tarifItem) {
if (tipe === 'RI') {
// Get tarif based on patient class
const kelas = currentEditingBilling.Kelas;
if (kelas === '1') {
tarif = tarifItem.Kelas1 || 0;
} else if (kelas === '2') {
tarif = tarifItem.Kelas2 || 0;
} else if (kelas === '3') {
tarif = tarifItem.Kelas3 || 0;
}
} else if (tipe === 'RJ') {
// Get tarif directly from TarifINACBG field
tarif = tarifItem.TarifINACBG || tarifItem.tarif_inacbg || 0;
}
}
inacbgCodes.push({ value: code, text: codeText, tarif: tarif });
codeSelect.value = '';
renderCodeList();
calculateTotalKlaim(); // Update total after adding code
}
// Render code list
function renderCodeList() {
const codeList = document.getElementById('codeList');
codeList.innerHTML = '';
if (inacbgCodes.length === 0) {
codeList.innerHTML = '<p class="text-muted small">Belum ada kode</p>';
return;
}
inacbgCodes.forEach((codeObj, index) => {
const badge = document.createElement('span');
badge.className = 'code-badge';
const tarifDisplay = codeObj.tarif ? `(Rp${codeObj.tarif.toLocaleString('id-ID')})` : '';
badge.innerHTML = `
${codeObj.text || codeObj.value} ${tarifDisplay}
<span class="remove-btn" onclick="removeInacbgCode(${index})">×</span>
`;
codeList.appendChild(badge);
});
}
// Calculate total klaim from selected codes
function calculateTotalKlaim() {
const total = inacbgCodes.reduce((sum, code) => sum + (code.tarif || 0), 0);
document.getElementById('totalKlaim').value = total.toFixed(0);
}
// Remove INACBG code
function removeInacbgCode(index) {
inacbgCodes.splice(index, 1);
renderCodeList();
calculateTotalKlaim(); // Update total after removing code
}
// Submit INACBG form
async function submitInacbgForm(e) {
e.preventDefault();
const tipeInacbg = document.getElementById('tipeInacbg').value.trim();
const totalKlaim = parseFloat(document.getElementById('totalKlaim').value) || 0;
// Validation
if (!currentEditingBilling) {
showAlert('danger', 'Data billing tidak ditemukan');
return;
}
if (inacbgCodes.length === 0) {
showAlert('danger', 'Tambahkan minimal satu kode INACBG');
return;
}
if (!tipeInacbg) {
showAlert('danger', 'Pilih tipe INACBG');
return;
}
if (totalKlaim === 0) {
showAlert('danger', 'Total klaim tidak boleh 0');
return;
}
// Prepare payload
const payload = {
id_billing: currentEditingBilling.id_billing,
tipe_inacbg: tipeInacbg,
kode_inacbg: inacbgCodes.map(c => c.value), // Extract just the codes
total_klaim: totalKlaim,
billing_sign: 'created' // or any status you want
};
try {
const res = await fetch(`${API_BASE}/admin/inacbg`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const result = await res.json();
if (!res.ok) {
throw new Error(result.error || result.message || 'Gagal menyimpan INACBG');
}
showAlert('success', 'INACBG berhasil disimpan');
setTimeout(() => {
bootstrap.Modal.getInstance(document.getElementById('editModal')).hide();
loadBillingData();
}, 1500);
} catch (err) {
console.error('Error:', err);
showAlert('danger', err.message);
}
}
// Show alert in modal
function showAlert(type, message) {
const alert = document.getElementById('formAlert');
alert.className = `alert alert-${type}`;
alert.textContent = message;
alert.classList.remove('d-none');
}
// Search billing
function searchBilling(e) {
const keyword = e.target.value.toLowerCase().trim();
if (keyword === '') {
renderBillingTable();
return;
}
const filtered = billingData.filter(b =>
(b.nama_pasien && b.nama_pasien.toLowerCase().includes(keyword)) ||
(b.id_pasien && b.id_pasien.toString().includes(keyword))
);
const tbody = document.getElementById('billingTableBody');
tbody.innerHTML = '';
if (filtered.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="4" class="text-center text-muted">Tidak ada hasil pencarian</td>
</tr>
`;
return;
}
filtered.forEach(billing => {
const row = document.createElement('tr');
const badgeColor = getBillingSignColor(billing.billing_sign);
const badgeClass = getBillingSignBadgeClass(billing.billing_sign);
row.innerHTML = `
<td>${billing.id_pasien || '-'}</td>
<td>
<a href="#" class="text-primary text-decoration-none" onclick="openEditModal(${billing.id_billing}); return false;">
${billing.nama_pasien || '-'}
</a>
</td>
<td>
<span class="billing-sign-badge ${badgeClass}" style="background-color: ${badgeColor};" title="${billing.billing_sign}"></span>
</td>
<td>
<button class="btn btn-sm btn-primary" onclick="openEditModal(${billing.id_billing})">
✎ Edit
</button>
</td>
`;
tbody.appendChild(row);
});
}
@@ -0,0 +1,310 @@
/* General Styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f5f5f5;
}
.container-fluid {
height: 100vh;
}
.row {
height: 100%;
}
/* Sidebar */
.sidebar {
background-color: #f8f9fa;
border-right: 1px solid #dee2e6;
padding: 20px;
overflow-y: auto;
max-height: 100vh;
}
.sidebar-header {
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 2px solid #007bff;
}
.sidebar-header h5 {
color: #333;
font-weight: 600;
}
.ruangan-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.ruangan-item {
padding: 10px 15px;
background-color: white;
border: 1px solid #dee2e6;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
color: #6c757d;
font-size: 0.95rem;
}
.ruangan-item:hover {
background-color: #e7f3ff;
border-color: #007bff;
color: #007bff;
}
.ruangan-item.active {
background-color: #007bff;
border-color: #007bff;
color: white;
font-weight: 600;
}
/* Main Content */
.main-content {
background-color: white;
padding: 30px;
overflow-y: auto;
max-height: 100vh;
}
.header {
margin-bottom: 30px;
}
.header h2 {
color: #333;
font-weight: 600;
margin-bottom: 10px;
}
.header .text-muted {
color: #6c757d;
}
.search-box input {
border: 1px solid #dee2e6;
border-radius: 20px;
padding: 10px 20px;
font-size: 0.95rem;
}
.search-box input:focus {
border-color: #007bff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
/* Billing Table */
.billing-table-container {
overflow-x: auto;
}
.table {
margin-bottom: 0;
}
.table thead th {
background-color: #f8f9fa;
border-bottom: 2px solid #dee2e6;
font-weight: 600;
color: #333;
padding: 15px;
}
.table tbody td {
padding: 15px;
vertical-align: middle;
}
.table tbody tr:hover {
background-color: #f8f9fa;
}
/* Billing Sign Badge */
.billing-sign-badge {
width: 20px;
height: 20px;
border-radius: 50%;
display: inline-block;
}
.billing-sign-badge.created {
background-color: #dc3545;
}
.billing-sign-badge.kuning {
background-color: #ffc107;
}
.billing-sign-badge.hijau {
background-color: #28a745;
}
.billing-sign-badge.merah {
background-color: #dc3545;
}
/* Modal Styles */
.modal-content {
border-radius: 8px;
border: none;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.modal-header {
border-bottom: 1px solid #dee2e6;
background-color: #f8f9fa;
}
.modal-header .modal-title {
font-weight: 600;
color: #333;
}
.modal-body h6 {
font-weight: 600;
color: #6c757d;
margin-bottom: 8px;
font-size: 0.9rem;
}
.modal-body .form-control {
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 10px 12px;
}
.modal-body .form-control:focus {
border-color: #007bff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
.modal-body .form-select {
border: 1px solid #dee2e6;
border-radius: 4px;
}
.modal-body .form-select:focus {
border-color: #007bff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
/* Code List */
.code-badge {
display: inline-block;
background-color: #e7f3ff;
border: 1px solid #007bff;
border-radius: 20px;
padding: 6px 12px;
margin-right: 8px;
margin-bottom: 8px;
font-size: 0.9rem;
color: #007bff;
}
.code-badge .remove-btn {
margin-left: 8px;
cursor: pointer;
font-weight: bold;
}
/* Buttons */
.btn-primary {
background-color: #007bff;
border-color: #007bff;
border-radius: 4px;
}
.btn-primary:hover {
background-color: #0056b3;
border-color: #0056b3;
}
.btn-success {
background-color: #28a745;
border-color: #28a745;
border-radius: 4px;
}
.btn-success:hover {
background-color: #218838;
border-color: #218838;
}
.btn-sm {
padding: 4px 8px;
font-size: 0.85rem;
}
/* Alerts */
.alert {
border-radius: 4px;
border: none;
}
.alert-success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.alert-danger {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.alert-info {
background-color: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
/* Responsive */
@media (max-width: 768px) {
.sidebar {
display: none;
}
.main-content {
padding: 15px;
}
.header h2 {
font-size: 1.5rem;
}
.table thead {
display: none;
}
.table tbody tr {
display: block;
margin-bottom: 15px;
border: 1px solid #dee2e6;
border-radius: 4px;
}
.table tbody td {
display: block;
text-align: right;
padding-left: 50%;
position: relative;
}
.table tbody td:before {
content: attr(data-label);
position: absolute;
left: 15px;
font-weight: 600;
color: #6c757d;
}
}