577 lines
20 KiB
Go
577 lines
20 KiB
Go
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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
}
|
|
|
|
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})
|
|
}
|