penambahan frontend

This commit is contained in:
2026-05-18 10:17:33 +07:00
parent 5533d1c935
commit 423a2d1095
7 changed files with 1676 additions and 273 deletions
+576
View File
@@ -0,0 +1,576 @@
package admin
import (
"api-service/internal/config"
"api-service/internal/database"
"context"
"database/sql"
"encoding/json"
"fmt"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
)
type Ruangan struct {
No int
Nama string
JumlahTT int
KodeKelas sql.NullString
KodeAplicare sql.NullString
NamaRuang sql.NullString
StAktif int
}
type AdminHandler struct {
db database.Service
}
type AdminHandlerConfig struct {
Config *config.Config
}
func NewAdminHandler(cfg AdminHandlerConfig) *AdminHandler {
return &AdminHandler{db: database.New(cfg.Config)}
}
func (h *AdminHandler) getRuangan(ctx context.Context) ([]Ruangan, error) {
db, err := h.db.GetDB("simrs")
if err != nil {
return nil, fmt.Errorf("koneksi simrs gagal: %w", err)
}
rows, err := db.QueryContext(ctx, `
SELECT no, nama, jumlah_tt, kode_kelas, kode_aplicare, nama_ruang, st_aktif
FROM m_ruang
WHERE subsistem LIKE '%RAWAT INAP%'
ORDER BY no
`)
if err != nil {
return nil, err
}
defer rows.Close()
var result []Ruangan
for rows.Next() {
var r Ruangan
if err := rows.Scan(&r.No, &r.Nama, &r.JumlahTT,
&r.KodeKelas, &r.KodeAplicare, &r.NamaRuang, &r.StAktif); err != nil {
return nil, err
}
result = append(result, r)
}
return result, rows.Err()
}
// GetPage — GET /admin/aplicares
func (h *AdminHandler) GetPage(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
ruangans, err := h.getRuangan(ctx)
if err != nil {
c.String(http.StatusInternalServerError, "Error: "+err.Error())
return
}
type RuanganJSON struct {
No int `json:"no"`
Nama string `json:"nama"`
JumlahTT int `json:"jumlah_tt"`
KodeKelas string `json:"kode_kelas"`
KodeAplicare string `json:"kode_aplicare"`
NamaRuang string `json:"nama_ruang"`
SudahMapping bool `json:"sudah_mapping"`
StAktif int `json:"st_aktif"`
}
var data []RuanganJSON
belumMapping := 0
for _, r := range ruangans {
sudah := r.KodeKelas.Valid && r.KodeKelas.String != "" &&
r.KodeAplicare.Valid && r.KodeAplicare.String != ""
if !sudah {
belumMapping++
}
data = append(data, RuanganJSON{
No: r.No,
Nama: r.Nama,
JumlahTT: r.JumlahTT,
KodeKelas: r.KodeKelas.String,
KodeAplicare: r.KodeAplicare.String,
NamaRuang: r.NamaRuang.String,
SudahMapping: sudah,
StAktif: r.StAktif,
})
}
jsonData, _ := json.Marshal(data)
html := `<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Aplicares</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f5f6fa; color: #333; }
.header { background: #1a73e8; color: white; padding: 16px 24px; display: flex; align-items: center; justify-content: space-between; }
.header h1 { font-size: 18px; font-weight: 600; }
.btn-add { background: white; color: #1a73e8; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-weight: 500; font-size: 14px; }
.stats { display: flex; gap: 16px; padding: 20px 24px; }
.stat-card { background: white; border-radius: 8px; padding: 16px 24px; flex: 1; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.stat-card .number { font-size: 28px; font-weight: 700; color: #1a73e8; }
.stat-card.success .number { color: #2ecc71; }
.stat-card.warning .number { color: #f4a261; }
.stat-card .label { font-size: 13px; color: #666; margin-top: 4px; }
.container { padding: 0 24px 24px; }
.search-bar { background: white; border-radius: 8px; padding: 12px 16px; margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
.search-bar input { border: 1px solid #ddd; border-radius: 6px; padding: 8px 12px; width: 100%; font-size: 14px; outline: none; }
.search-bar input:focus { border-color: #1a73e8; }
.table-wrap { background: white; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); overflow: hidden; }
table { width: 100%; border-collapse: collapse; }
th { background: #f8f9fa; padding: 12px 16px; text-align: left; font-size: 13px; font-weight: 600; color: #555; border-bottom: 1px solid #eee; white-space: nowrap; }
td { padding: 11px 16px; font-size: 14px; border-bottom: 1px solid #f0f0f0; }
tr:last-child td { border-bottom: none; }
tr:hover td { background: #f8f9ff; }
.badge { padding: 3px 10px; border-radius: 12px; font-size: 12px; font-weight: 500; white-space: nowrap; }
.badge-ok { background: #d4edda; color: #155724; }
.badge-no { background: #fff3cd; color: #856404; }
.btn-edit { background: #1a73e8; color: white; border: none; padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 13px; white-space: nowrap; }
.btn-edit:hover { background: #1557b0; }
.modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 100; align-items: center; justify-content: center; }
.modal-overlay.show { display: flex; }
.modal { background: white; border-radius: 12px; padding: 24px; width: 480px; max-width: 90vw; max-height: 90vh; overflow-y: auto; }
.modal h2 { font-size: 16px; margin-bottom: 4px; }
.modal .subtitle { font-size: 13px; color: #888; margin-bottom: 20px; }
.form-group { margin-bottom: 14px; }
.form-group label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 6px; color: #555; }
.form-group input, .form-group select { width: 100%; border: 1px solid #ddd; border-radius: 6px; padding: 9px 12px; font-size: 14px; outline: none; }
.form-group input:focus, .form-group select:focus { border-color: #1a73e8; }
.modal-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px; }
.btn-cancel { background: #f0f0f0; color: #333; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; }
.btn-save { background: #1a73e8; color: white; border: none; padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; font-weight: 500; }
.btn-save:hover { background: #1557b0; }
.toast { position: fixed; bottom: 24px; right: 24px; padding: 12px 20px; border-radius: 8px; color: white; font-size: 14px; z-index: 200; opacity: 0; transition: opacity .3s; pointer-events: none; }
.toast.show { opacity: 1; }
.toast-ok { background: #2ecc71; }
.toast-err { background: #e74c3c; }
</style>
</head>
<body>
<div class="header">
<h1>🏥 Admin Aplicares — Mapping Ruangan BPJS</h1>
<button class="btn-add" onclick="openCreate()">+ Tambah Ruangan</button>
</div>
<div class="stats">
<div class="stat-card">
<div class="number" id="statTotal">-</div>
<div class="label">Total Ruangan</div>
</div>
<div class="stat-card success">
<div class="number" id="statSudah">-</div>
<div class="label">Sudah Mapping</div>
</div>
<div class="stat-card warning">
<div class="number" id="statBelum">-</div>
<div class="label">Belum Mapping</div>
</div>
</div>
<div class="container">
<div class="search-bar">
<input type="text" id="search" placeholder="🔍 Cari nama ruangan..." oninput="filterTable()">
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>No</th>
<th>Nama Ruangan (SIMRS)</th>
<th>Kapasitas</th>
<th>Status</th>
<th>Kode Kelas</th>
<th>Kode BPJS</th>
<th>Status</th>
<th>Aksi</th>
</tr>
</thead>
<tbody id="tbody"></tbody>
</table>
</div>
</div>
<!-- Modal Edit -->
<div class="modal-overlay" id="modalEdit">
<div class="modal">
<h2>Edit Mapping Ruangan</h2>
<p class="subtitle" id="editSubtitle"></p>
<input type="hidden" id="editNo">
<div class="form-group">
<label>Kode Kelas BPJS *</label>
<select id="editKodeKelas">` + kelasOptions() + `</select>
</div>
<div class="form-group">
<label>Kode Ruang BPJS (kode_aplicare) *</label>
<input type="text" id="editKodeAplicare" placeholder="contoh: BARI1">
</div>
<div class="form-group">
<label>Nama Ruang BPJS</label>
<input type="text" id="editNamaRuang" placeholder="contoh: RUANG BARITO VIP">
</div>
<div class="form-group">
<label>Kapasitas (jumlah_tt)</label>
<input type="number" id="editKapasitas" placeholder="contoh: 10" min="0">
</div>
<div class="form-group">
<label>Status</label>
<select id="editStAktif">
<option value="1">Aktif</option>
<option value="0">Nonaktif</option>
</select>
</div>
<div class="modal-actions">
<button class="btn-cancel" onclick="closeModal('modalEdit')">Batal</button>
<button class="btn-save" onclick="saveEdit()">Simpan</button>
</div>
</div>
</div>
<!-- Modal Create -->
<div class="modal-overlay" id="modalCreate">
<div class="modal">
<h2>Tambah Ruangan Baru</h2>
<p class="subtitle">Data akan ditambahkan ke SIMRS + mapping BPJS</p>
<div class="form-group">
<label>Nama Ruangan (SIMRS) *</label>
<input type="text" id="createNama" placeholder="contoh: R.BARU KELAS 1">
</div>
<div class="form-group">
<label>Kapasitas *</label>
<input type="number" id="createKapasitas" placeholder="contoh: 10" min="1">
</div>
<div class="form-group">
<label>Kode Kelas BPJS *</label>
<select id="createKodeKelas">` + kelasOptions() + `</select>
</div>
<div class="form-group">
<label>Kode Ruang BPJS *</label>
<input type="text" id="createKodeAplicare" placeholder="contoh: BARI1">
</div>
<div class="form-group">
<label>Nama Ruang BPJS</label>
<input type="text" id="createNamaRuang" placeholder="contoh: RUANG BARITO VIP">
</div>
<div class="modal-actions">
<button class="btn-cancel" onclick="closeModal('modalCreate')">Batal</button>
<button class="btn-save" onclick="saveCreate()">Simpan</button>
</div>
</div>
</div>
<div class="toast" id="toast"></div>
<script>
const DATA = ` + string(jsonData) + `;
let filtered = [...DATA];
function init() {
document.getElementById('statTotal').textContent = DATA.length;
document.getElementById('statSudah').textContent = DATA.filter(r => r.sudah_mapping).length;
document.getElementById('statBelum').textContent = DATA.filter(r => !r.sudah_mapping).length;
renderTable(DATA);
}
function renderTable(rows) {
const tbody = document.getElementById('tbody');
if (!rows || rows.length === 0) {
tbody.innerHTML = '<tr><td colspan="7" style="text-align:center;padding:24px;color:#888">Tidak ada data</td></tr>';
return;
}
tbody.innerHTML = rows.map(r => {
const badge = r.sudah_mapping
? '<span class="badge badge-ok">✓ Sudah</span>'
: '<span class="badge badge-no">Belum</span>';
return '<tr>' +
'<td>' + r.no + '</td>' +
'<td>' + escHtml(r.nama) + '</td>' +
'<td>' + r.jumlah_tt + '</td>' +
'<td>' + badge + '</td>' +
'<td>' + escHtml(r.kode_kelas) + '</td>' +
'<td>' + escHtml(r.kode_aplicare) + '</td>' +
'<td>' + (r.st_aktif === 1
? '<span class="badge badge-ok">Aktif</span>'
: '<span class="badge badge-no">Nonaktif</span>') + '</td>' +
'<td><button class="btn-edit" onclick="openEdit(' + r.no + ')">Edit</button></td>' +
'</tr>';
}).join('');
}
function escHtml(s) {
if (!s) return '-';
return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}
function filterTable() {
const kw = document.getElementById('search').value.toLowerCase();
filtered = DATA.filter(r => r.nama.toLowerCase().includes(kw) || r.kode_aplicare.toLowerCase().includes(kw));
renderTable(filtered);
}
function openEdit(no) {
const r = DATA.find(x => x.no === no);
if (!r) return;
document.getElementById('editNo').value = r.no;
document.getElementById('editSubtitle').textContent = 'No. ' + r.no + ' — ' + r.nama;
document.getElementById('editKodeKelas').value = r.kode_kelas;
document.getElementById('editKodeAplicare').value = r.kode_aplicare;
document.getElementById('editNamaRuang').value = r.nama_ruang;
document.getElementById('editKapasitas').value = r.jumlah_tt;
document.getElementById('editStAktif').value = r.st_aktif;
document.getElementById('modalEdit').classList.add('show');
}
function openCreate() {
document.getElementById('createNama').value = '';
document.getElementById('createKapasitas').value = '';
document.getElementById('createKodeKelas').value = '';
document.getElementById('createKodeAplicare').value = '';
document.getElementById('createNamaRuang').value = '';
document.getElementById('modalCreate').classList.add('show');
}
function closeModal(id) {
document.getElementById(id).classList.remove('show');
}
function saveEdit() {
const no = document.getElementById('editNo').value;
const payload = {
kode_kelas: document.getElementById('editKodeKelas').value,
kode_aplicare: document.getElementById('editKodeAplicare').value.trim(),
nama_ruang: document.getElementById('editNamaRuang').value.trim(),
jumlah_tt: parseInt(document.getElementById('editKapasitas').value) || 0,
st_aktif: parseInt(document.getElementById('editStAktif').value)
};
if (!payload.kode_kelas || !payload.kode_aplicare) {
showToast('Kode Kelas dan Kode BPJS wajib diisi!', false);
return;
}
fetch('/api/v1/admin/aplicares/ruangan/' + no, {
method: 'PUT', headers: {'Content-Type':'application/json'},
body: JSON.stringify(payload)
}).then(r => r.json()).then(d => {
if (d.error) { showToast('Gagal: ' + d.error, false); return; }
showToast('Berhasil disimpan!', true);
closeModal('modalEdit');
setTimeout(() => location.reload(), 1000);
}).catch(() => showToast('Gagal konek ke server', false));
}
function saveCreate() {
const payload = {
nama: document.getElementById('createNama').value.trim(),
jumlah_tt: parseInt(document.getElementById('createKapasitas').value) || 0,
kode_kelas: document.getElementById('createKodeKelas').value,
kode_aplicare: document.getElementById('createKodeAplicare').value.trim(),
nama_ruang: document.getElementById('createNamaRuang').value.trim()
};
if (!payload.nama || !payload.kode_kelas || !payload.kode_aplicare) {
showToast('Field wajib belum diisi!', false);
return;
}
fetch('/api/v1/admin/aplicares/ruangan', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify(payload)
}).then(r => r.json()).then(d => {
if (d.error) { showToast('Gagal: ' + d.error, false); return; }
showToast('Ruangan berhasil ditambahkan!', true);
closeModal('modalCreate');
setTimeout(() => location.reload(), 1000);
}).catch(() => showToast('Gagal konek ke server', false));
}
function showToast(msg, ok) {
const t = document.getElementById('toast');
t.textContent = msg;
t.className = 'toast show ' + (ok ? 'toast-ok' : 'toast-err');
setTimeout(() => t.className = 'toast', 3000);
}
// Tutup modal klik luar
document.querySelectorAll('.modal-overlay').forEach(el => {
el.addEventListener('click', e => { if (e.target === el) el.classList.remove('show'); });
});
init();
</script>
</body>
</html>`
c.Header("Content-Type", "text/html; charset=utf-8")
c.String(http.StatusOK, html)
}
func kelasOptions() string {
opts := `<option value="">-- Pilih Kelas --</option>
<option value="NON">NON — -</option>
<option value="VVP">VVP — VVIP</option>
<option value="VIP">VIP — VIP</option>
<option value="UTM">UTM — UTAMA</option>
<option value="KL1">KL1 — KELAS I</option>
<option value="KL2">KL2 — KELAS II</option>
<option value="KL3">KL3 — KELAS III</option>
<option value="ICU">ICU — ICU</option>
<option value="ICC">ICC — ICCU</option>
<option value="NIC">NIC — NICU</option>
<option value="PIC">PIC — PICU</option>
<option value="IGD">IGD — IGD</option>
<option value="UGD">UGD — UGD</option>
<option value="SAL">SAL — RUANG BERSALIN</option>
<option value="HCU">HCU — HCU</option>
<option value="ISO">ISO — RUANG ISOLASI</option>`
return opts
}
// GetRuangan — GET /api/v1/admin/aplicares/ruangan (JSON)
func (h *AdminHandler) GetRuangan(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
defer cancel()
ruangans, err := h.getRuangan(ctx)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
type Resp struct {
No int `json:"no"`
Nama string `json:"nama"`
JumlahTT int `json:"jumlah_tt"`
KodeKelas string `json:"kode_kelas"`
KodeAplicare string `json:"kode_aplicare"`
NamaRuang string `json:"nama_ruang"`
SudahMapping bool `json:"sudah_mapping"`
}
var data []Resp
belum := 0
for _, r := range ruangans {
sudah := r.KodeKelas.Valid && r.KodeKelas.String != "" &&
r.KodeAplicare.Valid && r.KodeAplicare.String != ""
if !sudah {
belum++
}
data = append(data, Resp{
No: r.No, Nama: r.Nama, JumlahTT: r.JumlahTT,
KodeKelas: r.KodeKelas.String, KodeAplicare: r.KodeAplicare.String,
NamaRuang: r.NamaRuang.String, SudahMapping: sudah,
})
}
c.JSON(http.StatusOK, gin.H{
"total": len(data), "belum_mapping": belum,
"sudah_mapping": len(data) - belum, "data": data,
})
}
// UpdateRuangan — PUT /api/v1/admin/aplicares/ruangan/:no
func (h *AdminHandler) UpdateRuangan(c *gin.Context) {
no, err := strconv.Atoi(c.Param("no"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "no tidak valid"})
return
}
var body struct {
KodeKelas string `json:"kode_kelas"`
KodeAplicare string `json:"kode_aplicare"`
NamaRuang string `json:"nama_ruang"`
JumlahTT int `json:"jumlah_tt"`
StAktif int `json:"st_aktif"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if body.KodeKelas == "" || body.KodeAplicare == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "kode_kelas dan kode_aplicare wajib diisi"})
return
}
db, err := h.db.GetDB("simrs")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "koneksi DB gagal"})
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), 10*time.Second)
defer cancel()
result, err := db.ExecContext(ctx, `
UPDATE m_ruang SET kode_kelas=$1, kode_aplicare=$2, nama_ruang=$3, jumlah_tt=$4, st_aktif=$5 WHERE no=$6
`, body.KodeKelas, body.KodeAplicare, body.NamaRuang, body.JumlahTT, body.StAktif, no)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "update gagal: " + err.Error()})
return
}
rowsAffected, _ := result.RowsAffected()
if rowsAffected == 0 {
c.JSON(http.StatusOK, gin.H{"error": "tidak ada row yang terupdate, no=" + fmt.Sprintf("%d", no)})
return
}
c.JSON(http.StatusOK, gin.H{"message": "berhasil diupdate", "no": no, "rows_affected": rowsAffected})
}
// CreateRuangan — POST /api/v1/admin/aplicares/ruangan
func (h *AdminHandler) CreateRuangan(c *gin.Context) {
var body struct {
Nama string `json:"nama"`
JumlahTT int `json:"jumlah_tt"`
KodeKelas string `json:"kode_kelas"`
KodeAplicare string `json:"kode_aplicare"`
NamaRuang string `json:"nama_ruang"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if body.Nama == "" || body.KodeKelas == "" || body.KodeAplicare == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "nama, kode_kelas, kode_aplicare wajib diisi"})
return
}
db, err := h.db.GetDB("simrs")
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "koneksi DB gagal"})
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), 10*time.Second)
defer cancel()
// Auto generate no = MAX(no) + 1
var maxNo int
err = db.QueryRowContext(ctx, `SELECT COALESCE(MAX(no), 0) FROM m_ruang`).Scan(&maxNo)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "gagal generate no: " + err.Error()})
return
}
newNo := maxNo + 1
_, err = db.ExecContext(ctx, `
INSERT INTO m_ruang (no, nama, jumlah_tt, subsistem, st_aktif, kode_kelas, kode_aplicare, nama_ruang)
VALUES ($1, $2, $3, 'RAWAT INAP', 1, $4, $5, $6)
`, newNo, body.Nama, body.JumlahTT, body.KodeKelas, body.KodeAplicare, body.NamaRuang)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "insert gagal: " + err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "ruangan berhasil ditambahkan", "no": newNo})
}
+13
View File
@@ -1,6 +1,7 @@
package v1
import (
"api-service/internal/admin"
AplicareHandler "api-service/internal/aplicare"
"api-service/internal/config"
"api-service/internal/database"
@@ -145,6 +146,18 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
ag.GET("/logs", aplicaresHandler.GetSyncLogs)
}
adminHandler := admin.NewAdminHandler(admin.AdminHandlerConfig{
Config: cfg,
})
adminGroup := v1.Group("/admin/aplicares")
{
adminGroup.GET("/ruangan", adminHandler.GetRuangan) // JSON API
adminGroup.PUT("/ruangan/:no", adminHandler.UpdateRuangan) // update mapping
adminGroup.POST("/ruangan", adminHandler.CreateRuangan)
}
router.GET("/admin", adminHandler.GetPage)
// =============================================================================
// PROTECTED ROUTES (Authentication Required)
// =============================================================================
+39
View File
@@ -0,0 +1,39 @@
package ruang
import (
"context"
"fmt"
)
func (s *SimrsDB) GetMRuangan(ctx context.Context) ([]Ruangan, error) {
db, err := s.db.GetDB("simrs")
if err != nil {
return nil, fmt.Errorf("koneksi simrs gagal: %w", err)
}
query := `
SELECT no, nama, jumlah_tt,kode_aplicare, nama_ruang, kode_kelas
FROM m_ruang
where st_aktif = 1
ORDER BY no
`
rows, err := db.QueryContext(ctx, query)
if err != nil {
return nil, fmt.Errorf("query m_ruang gagal: %w", err)
}
defer rows.Close()
var result []Ruangan
for rows.Next() {
var r Ruangan
if err := rows.Scan(
&r.No, &r.Nama, &r.JumlahTT,
&r.KodeRuang, &r.NamaRuang, &r.KelasRuang,
); err != nil {
return nil, fmt.Errorf("scan m_ruang gagal: %w", err)
}
result = append(result, r)
}
return result, rows.Err()
}
+12
View File
@@ -0,0 +1,12 @@
package ruang
import "database/sql"
type Ruangan struct {
No int `db:"no"`
Nama string `db:"nama"`
JumlahTT int `db:"jumlah_tt"`
KodeRuang sql.NullString `db:"kode_aplicare"` // diisi manual, dikirim ke BPJS
NamaRuang sql.NullString `db:"nama_ruang"` // diisi manual, dikirim ke BPJS
KelasRuang sql.NullString `db:"kode_kelas"` // diisi manual, dikirim ke BPJS
}
+44
View File
@@ -0,0 +1,44 @@
package ruang
import (
"api-service/internal/config"
"api-service/internal/database"
"api-service/pkg/logger"
"context"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"net/http"
"sync"
"time"
)
type MruangHandler struct {
simrs *SimrsDB
validator *validator.Validate
logger logger.Logger
cfg *config.Config
once sync.Once
interval time.Duration
}
type MruangHandlerConfig struct {
Config *config.Config
Logger logger.Logger
Validator *validator.Validate
}
type SimrsDB struct {
db database.Service
}
func (h *MruangHandler) NewMruangHandler(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), 120*time.Second)
defer cancel()
ruangans, err := h.simrs.GetMRuangan(ctx)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, ruangans)
}