penambahan frontend
This commit is contained in:
@@ -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,'&').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})
|
||||
}
|
||||
Reference in New Issue
Block a user