MIF_E31231302_WEBSITE/resources/views/data-pasien.blade.php

3708 lines
105 KiB
PHP

<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<title>Data Pasien - Sistem TBC</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="{{ csrf_token() }}">
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap" rel="stylesheet">
<script src="https://unpkg.com/lucide@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body {
display: flex;
background: #f5f6fa;
min-height: 100vh;
}
/* SIDEBAR */
.sidebar {
width: 250px;
height: 100vh;
background: #c40000;
padding-top: 30px;
position: fixed;
z-index: 100;
}
.sidebar h2 {
color: white;
text-align: center;
margin-bottom: 40px;
font-size: 20px;
font-weight: 700;
}
.menu {
list-style: none;
}
.menu a {
text-decoration: none;
display: flex;
align-items: center;
padding: 15px 25px;
color: white;
transition: 0.3s;
gap: 15px;
}
.menu li:hover a,
.menu li.active a {
background: white;
color: #c40000;
border-radius: 30px 0 0 30px;
margin-left: 10px;
}
/* CONTENT */
.main-content {
margin-left: 250px;
padding: 40px;
width: calc(100% - 250px);
}
.header-wrapper {
border-bottom: 1px solid #e0e0e0;
margin-bottom: 30px;
padding-bottom: 10px;
}
.header-title {
font-size: 28px;
font-weight: 700;
color: #c40000;
display: inline-block;
padding-bottom: 10px;
}
.top-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
}
.action-header-left {
display: flex;
gap: 12px;
}
.btn-tambah {
background: #c40000;
color: white;
padding: 12px 20px;
border-radius: 10px;
border: none;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
transition: 0.3s;
}
/* Accordion Filter Styles */
.filter-accordion {
background: white;
border-radius: 15px;
margin-bottom: 25px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
overflow: hidden;
border: 1px solid #eee;
}
.filter-header {
padding: 15px 25px;
background: #fff;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
transition: 0.3s;
}
.filter-header:hover {
background: #fefefe;
}
.filter-header h4 {
font-size: 15px;
color: #333;
display: flex;
align-items: center;
gap: 10px;
}
.filter-content {
padding: 0 25px;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease-out, padding 0.3s ease;
background: #fff;
}
.filter-content.active {
padding: 20px 25px;
max-height: 500px;
border-top: 1px solid #f5f5f5;
}
.filter-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
align-items: flex-end;
}
.filter-group {
display: flex;
flex-direction: column;
gap: 6px;
}
.filter-group label {
font-size: 12px;
font-weight: 600;
color: #64748b;
}
.filter-group input,
.filter-group select {
padding: 10px;
border: 1px solid #ddd;
border-radius: 8px;
outline: none;
font-size: 13px;
}
.filter-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 15px;
border-top: 1px dashed #eee;
padding-top: 15px;
}
.btn-cari {
background: #c40000;
color: white;
padding: 10px 20px;
border-radius: 8px;
border: none;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
}
.btn-export {
background: #27ae60;
color: white;
padding: 10px 20px;
border-radius: 8px;
border: none;
font-weight: 600;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
}
.btn-reset {
background: #64748b;
color: white;
padding: 10px 20px;
border-radius: 8px;
border: none;
font-weight: 600;
cursor: pointer;
text-decoration: none;
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
}
.search-wrapper {
position: relative;
width: 300px;
display: flex;
align-items: center;
}
.search-wrapper .icon-container {
position: absolute;
left: 15px;
display: flex;
align-items: center;
pointer-events: none;
z-index: 9;
color: #888;
}
.search-wrapper input {
width: 100%;
padding: 12px 15px 12px 45px !important;
border-radius: 12px;
border: 1px solid #ddd;
background: #fff;
outline: none;
font-size: 14px;
}
.table-container {
background: white;
border-radius: 20px;
padding: 20px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.05);
width: 100%;
}
/* WRAPPER SCROLL */
.table-scroll {
width: 100%;
}
table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
th,
td {
padding: 14px 12px;
text-align: left;
vertical-align: middle;
font-size: 13px;
word-break: break-word;
}
th {
background-color: #fcfcfc;
color: #333;
font-weight: 600;
border-bottom: 2px solid #f0f0f0;
}
td {
border-bottom: 1px solid #eee;
color: #555;
background: white;
}
.col-no {
width: 5%;
text-align: center;
}
.col-nama {
width: 18%;
}
.col-nik {
width: 12%;
}
.col-tgl {
width: 10%;
}
.col-jk {
width: 5%;
text-align: center;
}
.col-alamat {
width: 20%;
}
.col-telp {
width: 10%;
}
.col-email {
width: 12%;
}
.col-status {
width: 10%;
}
.col-aksi {
width: 8%;
text-align: center;
}
/* KOLOM FILE */
.col-file {
min-width: 160px;
text-align: center;
}
.status-select {
padding: 8px 12px;
border-radius: 8px;
border: 1px solid #e2e8f0;
background: #f8fafc;
font-size: 12px;
font-weight: 600;
color: #475569;
cursor: pointer;
width: 100%;
}
/* ICON DENGAN BACKGROUND (MODERN) */
.action-btns {
display: flex;
gap: 8px;
justify-content: center;
}
.btn-icon {
width: 36px;
height: 36px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
border: none;
cursor: pointer;
transition: 0.3s;
}
.btn-icon.info {
background: #e8f5e9;
color: #2e7d32;
}
.btn-icon.info:hover {
background: #2e7d32;
color: white;
}
.btn-icon.edit {
background: #e3f2fd;
color: #1565c0;
}
.btn-icon.edit:hover {
background: #1565c0;
color: white;
}
.btn-icon.trash {
background: #ffebee;
color: #c62828;
}
.btn-icon.trash:hover {
background: #c62828;
color: white;
}
.pagination-wrapper {
margin-top: 30px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
background: #f8fafc;
border-radius: 12px;
}
.pagination-wrapper .text-muted {
font-size: 14px;
color: #64748b !important;
}
.pagination {
display: flex;
list-style: none;
gap: 8px;
margin: 0;
padding: 0;
}
.page-link {
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 40px;
padding: 0 14px;
border-radius: 10px;
background: white;
border: 1px solid #e2e8f0;
color: #475569;
text-decoration: none;
font-weight: 500;
font-size: 14px;
transition: all 0.2s ease;
}
.page-link:hover {
background: #fff1f1;
border-color: #c40000;
color: #c40000;
transform: translateY(-2px);
}
.page-item.active .page-link {
background: #c40000;
border-color: #c40000;
color: white;
box-shadow: 0 4px 12px rgba(196, 0, 0, 0.25);
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.4);
display: none;
justify-content: center;
align-items: center;
z-index: 1000;
backdrop-filter: blur(4px);
}
.modal-content {
background: white;
padding: 30px;
border-radius: 20px;
width: 650px;
max-height: 90vh;
overflow-y: auto;
}
.modal-card {
background: #fff;
border: 1px solid #eee;
border-radius: 18px;
padding: 22px;
margin-bottom: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.04);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25px;
}
.modal-header h3 {
color: #c40000;
font-size: 20px;
}
.form-row {
display: flex;
gap: 15px;
margin-bottom: 15px;
}
.form-group {
flex: 1;
display: flex;
flex-direction: column;
gap: 6px;
}
.form-group label {
font-size: 13px;
font-weight: 600;
color: #444;
}
.form-group input,
.form-group select {
padding: 12px;
border: 1px solid #ddd;
border-radius: 10px;
outline: none;
font-size: 14px;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 30px;
}
.btn {
padding: 12px 25px;
border-radius: 10px;
cursor: pointer;
font-weight: 600;
border: none;
font-size: 14px;
}
.btn-cancel {
background: #f3f4f6;
color: #4b5563;
}
.btn-submit {
background: #c40000;
color: white;
}
.swal2-popup.modern-popup {
width: 360px !important;
border-radius: 26px !important;
padding: 1.5rem 1.3rem 1.3rem !important;
background: rgba(255, 255, 255, 0.98) !important;
backdrop-filter: blur(18px) !important;
-webkit-backdrop-filter: blur(18px) !important;
box-shadow: 0 25px 60px rgba(15, 23, 42, 0.18) !important;
border: 1px solid rgba(255, 255, 255, 0.7) !important;
}
.swal2-icon.swal2-warning {
border-color: rgba(196, 0, 0, 0.15) !important;
color: #c40000 !important;
width: 68px !important;
height: 68px !important;
margin-top: 5px !important;
}
.swal2-title.title-swal {
font-size: 20px !important;
font-weight: 700 !important;
color: #0f172a !important;
padding: 0 !important;
margin-top: 10px !important;
line-height: 1.4 !important;
}
.swal2-html-container {
font-size: 13px !important;
color: #64748b !important;
line-height: 1.7 !important;
margin: 10px 0 5px 0 !important;
padding: 0 10px !important;
}
.swal2-actions {
width: 100% !important;
display: flex !important;
gap: 10px !important;
margin-top: 20px !important;
padding: 0 5px !important;
}
.btn-confirm-swal {
flex: 1 !important;
background: linear-gradient(135deg, #c40000, #ff3b3b) !important;
color: white !important;
padding: 11px 14px !important;
border-radius: 14px !important;
font-weight: 600 !important;
font-size: 13px !important;
border: none !important;
margin: 0 !important;
box-shadow: 0 10px 25px rgba(196, 0, 0, 0.25) !important;
transition: all 0.25s ease !important;
}
.btn-confirm-swal:hover {
transform: translateY(-2px) scale(1.02) !important;
box-shadow: 0 14px 30px rgba(196, 0, 0, 0.35) !important;
}
.btn-cancel-swal {
flex: 1 !important;
background: #f8fafc !important;
color: #475569 !important;
padding: 11px 14px !important;
border-radius: 14px !important;
font-weight: 600 !important;
font-size: 13px !important;
border: 1px solid #e2e8f0 !important;
margin: 0 !important;
transition: all 0.25s ease !important;
}
.btn-cancel-swal:hover {
background: #eef2f7 !important;
transform: translateY(-2px) !important;
}
.swal2-success-ring {
border: 0.25em solid rgba(34, 197, 94, 0.25) !important;
}
.swal2-popup.swal2-toast {
border-radius: 14px !important;
}
.custom-swal-popup {
border-radius: 24px !important;
padding: 25px !important;
box-shadow:
0 20px 60px rgba(0, 0, 0, 0.18) !important;
border:
1px solid rgba(255, 255, 255, 0.7);
backdrop-filter: blur(10px);
}
.custom-swal-title {
font-size: 24px !important;
font-weight: 700 !important;
color: #0f172a !important;
}
.custom-swal-button {
background: linear-gradient(135deg,
#2563eb,
#1d4ed8) !important;
color: white !important;
border: none !important;
padding:
12px 28px !important;
border-radius: 14px !important;
font-size: 15px !important;
font-weight: 600 !important;
transition: 0.3s ease;
box-shadow:
0 8px 20px rgba(37, 99, 235, 0.35);
}
.custom-swal-button:hover {
transform: translateY(-2px);
box-shadow:
0 12px 25px rgba(37, 99, 235, 0.45);
}
</style>
</head>
<body>
<div class="sidebar">
<h2>Sistem TBC</h2>
<ul class="menu">
<li><a href="{{ url('/dashboard') }}"><i data-lucide="layout-dashboard"></i> Dashboard</a></li>
<li class="active"><a href="{{ url('/data-pasien') }}"><i data-lucide="users"></i> Data Pasien</a></li>
<li><a href="{{ url('/skrining') }}"><i data-lucide="stethoscope"></i> Skrining</a></li>
@if(session('role') == 'super_admin')
<li>
<a href="{{ url('/kelola-admin') }}">
<i data-lucide="shield"></i> Kelola Admin
</a>
</li>
<li>
<a href="{{ url('/riwayat-aktivitas') }}">
<i data-lucide="activity"></i> Riwayat Aktivitas
</a>
</li>
@endif
<li>
<a href="{{ url('/pengaturan-akun') }}"><i data-lucide="user"></i> Akun</a>
</li>
</div>
<div class="main-content">
<div class="header-wrapper">
<h1 class="header-title">Data Pasien</h1>
</div>
<div class="top-bar">
<div class="action-header-left">
<button class="btn-tambah" onclick="openModalTambah()">
<i data-lucide="plus" size="18"></i> Tambah Pasien Baru
</button>
</div>
<div class="search-wrapper">
<div class="icon-container"><i data-lucide="search" size="18"></i></div>
<input type="text" id="searchInput" placeholder="Cari Nama atau NIK..." value="{{ request('search') }}">
</div>
</div>
<!-- Kotak Filtering (Accordion) -->
<div class="filter-accordion">
<div class="filter-header" onclick="toggleFilter()">
<h4><i data-lucide="filter" size="18"></i> Pencarian Lanjutan & Export</h4>
<i data-lucide="chevron-down" id="filterIcon"></i>
</div>
<div class="filter-content {{ (request('status') || request('desa') || request('start_date')) ? 'active' : '' }}" id="filterContent">
<form action="{{ url('/data-pasien') }}" method="GET" id="filterForm">
<input type="hidden" name="search" id="hiddenSearch" value="{{ request('search') }}">
<div class="filter-grid">
<div class="filter-group">
<label>Status Kesehatan</label>
<select name="status" id="filterStatus">
<option value="">Semua Status</option>
<option value="Masih Proses" {{ request('status') == 'Masih Proses' ? 'selected' : '' }}>Masih Proses</option>
<option value="Sembuh" {{ request('status') == 'Sembuh' ? 'selected' : '' }}>Sembuh</option>
<option value="Meninggal" {{ request('status') == 'Meninggal' ? 'selected' : '' }}>Meninggal</option>
<option value="Gagal" {{ request('status') == 'Gagal' ? 'selected' : '' }}>Gagal</option>
</select>
</div>
<div class="filter-group">
<label>Data Per Desa</label>
<input type="text" name="desa" placeholder="Nama Desa..." value="{{ request('desa') }}">
</div>
<div class="filter-group">
<label>Tanggal Mulai Pengobatan</label>
<input type="date" max="{{ date('Y-m-d') }}" name="start_date" value="{{ request('start_date') }}">
</div>
<div class="filter-group">
<label>Tanggal Selesai Pengobatan</label>
<input type="date" name="end_date" value="{{ request('end_date') }}">
</div>
@if(session('role') == 'super_admin')
<div class="filter-group">
<label>Filter Fasyankes</label>
<select name="fasyankes">
<option value="">Semua Fasyankes</option>
@foreach($listFasyankes as $f)
<option
value="{{ $f->nama_fasyankes }}"
{{ request('fasyankes') == $f->nama_fasyankes ? 'selected' : '' }}>
{{ $f->nama_fasyankes }}
</option>
@endforeach
</select>
</div>
@endif
</div>
<div class="filter-actions">
<a href="{{ url('/data-pasien') }}" class="btn-reset">
<i data-lucide="refresh-cw" size="16"></i> Reset Filter
</a>
<button type="button" class="btn-export" onclick="submitExport()">
<i data-lucide="download" size="16"></i> Export Excel
</button>
<button type="submit" class="btn-cari">
<i data-lucide="search" size="16"></i> Cari Data
</button>
</div>
</form>
</div>
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th class="col-no">No.</th>
<th class="col-nama">Nama</th>
<th class="col-nik">NIK</th>
<th class="col-tgl">Tgl Lahir</th>
<th class="col-jk">L/P</th>
<th class="col-alamat">Alamat</th>
<th class="col-telp">No. Telp</th>
@if(session('role') == 'super_admin')
<th class="col-email">Email</th>
<th class="col-status">Fasyankes</th>
@endif
<th class="col-aksi">Aksi</th>
</tr>
</thead>
<tbody id="pasienTableBody">
@foreach($pasiens as $p)
<tr>
<td class="col-no">{{ ($pasiens->currentPage() - 1) * $pasiens->perPage() + $loop->iteration }}</td>
<td class="col-nama">{{ $p->nama_lengkap }}</td>
<td>{{ $p->nik }}</td>
<td>{{ $p->tgl_lahir ?? '-' }}</td>
<td style="text-align:center;">{{ $p->jenis_kelamin }}</td>
<td>{{ $p->alamat }}</td>
<td>{{ $p->no_telp }}</td>
@if(session('role') == 'super_admin')
<td>{{ $p->email ?? '-' }}</td>
<td>{{ $p->nama_fasyankes ?? '-' }}</td>
@endif
<td class="col-aksi">
<div class="action-btns">
<button class="btn-icon info" onclick="window.location.href='/detail-pasien/{{ $p->id_medis }}'" title="Detail">
<i data-lucide="info" size="18"></i>
</button>
<button
type="button"
class="btn-icon edit btn-edit-pasien"
data-item='@json($p)'
onclick="prepareEdit(this.dataset.item)"
title="Edit">
<i data-lucide="edit-3" size="18"></i>
</button>
<button class="btn-icon info"
onclick="openPindahFasyankes({{ $p->id_user }}, '{{ $p->nama_lengkap }}', '{{ $p->nama_fasyankes }}')"
title="Pindah Fasyankes">
<i data-lucide="hospital" size="18"></i>
</button>
<button class="btn-icon trash" onclick="aksiHapus(this, {{ $p->id_user }})" title="Hapus">
<i data-lucide="trash-2" size="18"></i>
</button>
</div>
</td>
</tr>
@endforeach
</tbody>
</table>
<div class="pagination-wrapper">
<div class="pagination-info">
<span class="text-muted">
Menampilkan <b>{{ $pasiens->firstItem() }}</b> sampai <b>{{ $pasiens->lastItem() }}</b> dari <b>{{ $pasiens->total() }}</b> pasien
</span>
</div>
<div class="pagination-links">
{{ $pasiens->appends(request()->input())->links('pagination::bootstrap-4') }}
</div>
</div>
</div>
</div>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
@csrf
</form>
<!-- Modal Pasien -->
<div class="modal-overlay" id="modalPasien">
<div class="modal-content">
<div class="modal-header">
<h3 id="modalTitle">Tambah Pasien Baru</h3>
<button class="btn-icon info" onclick="closeModal()" style="width: 30px; height: 30px;"><i data-lucide="x" size="18"></i></button>
</div>
<form id="pasienForm" method="POST" enctype="multipart/form-data">
<div class="modal-card">
@csrf
<input type="hidden" name="form_mode" id="form_mode" value="create">
<input type="hidden" name="id_user" id="form_id_user">
<!-- ========================= -->
<!-- DATA IDENTITAS -->
<!-- ========================= -->
<div style="margin-bottom:25px;">
<h4 style="color:#c40000;margin-bottom:15px;">
Data Identitas Pasien
</h4>
<div class="form-row">
<div class="form-group">
<label>Nama Lengkap Pasien</label>
<input type="text" name="nama" id="form_nama" required>
</div>
<div class="form-group">
<label>NIK</label>
<input type="text" name="nik" id="form_nik" maxlength="16" required>
<small id="nikError" style="color:red; display:none;">
NIK harus 16 digit!
</small>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Email</label>
<input type="email" name="email" id="form_email" required>
</div>
<div class="form-group">
<label>No. Telepon</label>
<input type="text" name="telp" id="form_telp" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Tanggal Lahir</label>
<input type="date" max="{{ date('Y-m-d') }}" name="tgl_lahir" id="form_tgl_lahir" required>
</div>
<div class="form-group">
<label>Jenis Kelamin</label>
<select name="jenis_kelamin" id="form_jk" required>
<option value="">Pilih</option>
<option value="L">Laki-laki</option>
<option value="P">Perempuan</option>
</select>
</div>
</div>
@if(session('role') == 'super_admin')
<div class="form-row">
<div class="form-group">
<label>Pilih Fasyankes</label>
<select name="nama_fasyankes" id="form_fasyankes" required>
<option value="">Pilih Fasyankes</option>
@foreach($listFasyankes as $f)
<option value="{{ $f->nama_fasyankes }}">
{{ $f->nama_fasyankes }}
</option>
@endforeach
</select>
</div>
</div>
@endif
<!-- ALAMAT PINDAH KE IDENTITAS -->
<div style="margin-top:20px;">
<p style="font-weight:700;margin-bottom:15px;">
Data Alamat
</p>
<div class="form-row">
<div class="form-group">
<label>Kecamatan</label>
<select name="kecamatan"
id="form_kecamatan"
required>
<option value="">
Pilih Kecamatan
</option>
</select>
</div>
<div class="form-group">
<label>Desa</label>
<select name="desa"
id="form_desa"
required
disabled>
<option value="">
Pilih Desa
</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Dusun</label>
<input
type="text"
name="dusun"
id="form_dusun"
placeholder="Masukkan dusun"
required>
</div>
<div class="form-group">
<label>RT / RW</label>
<input type="text"
name="rtrw"
id="rtrw_input"
required>
</div>
</div>
</div>
</div>
</div>
<!-- ========================= -->
<!-- DATA MEDIS -->
<!-- ========================= -->
<div class="modal-card">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:15px;">
<h4 style="color:#c40000;">
Data Medis Pengobatan
</h4>
<div id="pengobatanActionWrapper">
<button type="button"
id="btnTambahPengobatan"
style="display:none;
background:#c40000;
color:white;
border:none;
padding:10px 15px;
border-radius:10px;
font-weight:600;
cursor:pointer;
transition:0.2s;
"
onclick="tambahRiwayatPengobatan()">
+ Tambah Pengobatan
</button>
<small
id="notePengobatan"
style="
display:none;
margin-top:8px;
color:#c40000;
font-size:12px;
font-weight:600;
">
</small>
</div>
</div>
<div id="containerRiwayatPengobatan"></div>
</div>
<div class="modal-footer">
<button type="button"
class="btn btn-cancel"
onclick="closeModal()">
Batal
</button>
<button type="submit"
class="btn btn-submit"
id="btnSubmitForm">
Simpan Pasien
</button>
</div>
</form>
</div>
</div>
<!-- MODAL PINDAH FASYANKES -->
<div class="modal-overlay" id="modalPindahFasyankes">
<div class="modal-content" style="width:500px;">
<div class="modal-header">
<h3>Pindahkan Pasien</h3>
<button class="btn-icon info"
onclick="closeModalPindah()"
style="width:30px;height:30px;">
<i data-lucide="x" size="18"></i>
</button>
</div>
<form id="formPindahFasyankes">
@csrf
<input type="hidden" id="pindah_id_user">
<div class="form-group" style="margin-bottom:20px;">
<label>Nama Pasien</label>
<input type="text" id="pindah_nama" readonly style="background:#f8fafc;">
</div>
<div class="form-group" style="margin-bottom:20px;">
<label>Fasyankes Saat Ini</label>
<input type="text" id="pindah_fasyankes_lama" readonly style="background:#f8fafc;">
</div>
<div class="form-group">
<label>Pindah Ke Fasyankes</label>
<select id="pindah_fasyankes_baru" required>
<option value="">Pilih Fasyankes</option>
@foreach($listFasyankes as $f)
<option value="{{ $f->nama_fasyankes }}">
{{ $f->nama_fasyankes }}
</option>
@endforeach
</select>
</div>
<div class="modal-footer">
<button type="button"
class="btn btn-cancel"
onclick="closeModalPindah()">
Batal
</button>
<button type="submit"
class="btn btn-submit">
Pindahkan
</button>
</div>
</form>
</div>
</div>
<script>
function parseDateSafe(dateStr) {
if (!dateStr) return null;
const parts = dateStr.split('-');
return new Date(parts[0], parts[1] - 1, parts[2]);
}
// DEFINE GLOBAL VARIABLES
const modal = document.getElementById('modalPasien');
const form = document.getElementById('pasienForm');
const today = new Date().toISOString().split('T')[0];
const wilayahJember = {
"AJUNG": {
"AJUNG": [
"KRAJAN",
"AJUNG KULON",
"AJUNG WETAN",
"CURAH KATES",
"GUMUK KERANG",
"KIDUL BESUK",
"KLANCENG",
"LIMBUNGSARI",
"SUMURAN"
],
"KLOMPANGAN": [
"KRAJAN",
"CURAH KATES",
"DURENAN",
"GRINTINGAN",
"GUMUK KEMBAR",
"PONDOK LABU",
"SUMURAN"
],
"MANGARAN": [
"KRAJAN",
"CURAH MANIS",
"CURAH TEPAS",
"GUMUK WULUH",
"KEBUN PRING",
"LONCATAN",
"PATEMON",
"RENTENG",
"SIDOMULYO"
],
"PANCAKARYA": [
"CURAH RENTENG",
"CURAH WELUT",
"GUMUK SEGAWE",
"KEDUNG KWALI",
"KRASAK",
"KRESEK"
],
"ROWO INDAH": [
"LANGSEPAN",
"ROWO"
],
"SUKAMAKMUR": [
"CURAH KENDAL",
"CURAH REJO",
"MANGARAN",
"LANGSATAN",
"PLALANGAN"
],
"WIROWONGSO": [
"BESUK",
"PENANGGUNGAN",
"RENES",
"SUMBEREJO"
]
},
"AMBULU": [
"SUMBERREJO",
"ANDONGSARI",
"SABRANG",
"AMBULU",
"PONTANG",
"KARANGANYAR",
"TEGALSARI"
],
"ARJASA": [
"KEMUNING LOR",
"DARSONO",
"ARJASA",
"BITING",
"CANDIJATI",
"KAMAL"
],
"BALUNG": [
"KARANG DUREN",
"KARANG SEMANDING",
"TUTUL",
"BALUNG KULON",
"BALUNG KIDUL",
"BALUNG LOR",
"GUMELAR",
"CURAH LELE"
],
"BANGSALSARI": [
"KARANGSONO",
"SUKOREJO",
"PETUNG",
"TISNOGAMBAR",
"LANGKAP",
"BANGSALSARI",
"GAMBIRONO",
"CURAH KALONG",
"TUGUSARI",
"BANJARSARI",
"BADEAN"
],
"GUMUKMAS": [
"KEPANJEN",
"MAYANGAN",
"MENAMPU",
"BAGOREJO",
"GUMUKMAS",
"PURWOSARI",
"TEMBOKREJO",
"KARANG REJO"
],
"JELBUK": [
"PANDUMAN",
"JELBUK",
"SUKOWIRYO",
"SUGER KIDUL",
"SUKO JEMBER",
"SUCO PENGEPOK"
],
"JENGGAWAH": [
"KEMUNINGSARI KIDUL",
"KERTONEGORO",
"JATISARI",
"SRUNI",
"CANGKRING",
"WONOJATI",
"JENGGAWAH",
"JATIMULYO"
],
"JOMBANG": [
"KETING",
"JOMBANG",
"PADOMASAN",
"NGAMPELREJO",
"WRINGIN AGUNG",
"SARI MULYO"
],
"KALISAT": [
"GAMBIRAN",
"PLALANGAN",
"AJUNG",
"GLAGAHWERO",
"SUMBER JERUK",
"GUMUKSARI",
"PATEMPURAN",
"KALISAT",
"SUMBER KETEMPAH",
"SUKORENO",
"SUMBER KALONG",
"SEBANEN"
],
"KALIWATES": [
"MANGLI",
"SEMPUSARI",
"KALIWATES",
"TEGAL BESAR",
"JEMBER KIDUL",
"KEPATIHAN",
"KEBON AGUNG"
],
"KENCONG": [
"PASEBAN",
"CAKRU",
"KRATON",
"WONOREJO",
"KENCONG"
],
"LEDOKOMBO": [
"SUREN",
"SUMBER SALAK",
"SUMBER BULUS",
"SUMBER LESUNG",
"LEMBENGAN",
"SUMBER ANGET",
"LEDOKOMBO",
"SLATENG",
"SUKOGIDRI",
"KARANG PAITON"
],
"MAYANG": [
"SEPUTIH",
"SIDOMUKTI",
"SUMBER KEJAYAN",
"TEGALREJO",
"TEGALWARU",
"MAYANG",
"MRAWAN"
],
"MUMBULSARI": [
"KAWANGREJO",
"TAMANSARI",
"SUCO",
"LAMPEJI",
"MUMBULSARI",
"LENGKONG",
"KARANGKEDAWUNG"
],
"PAKUSARI": [
"KERTOSARI",
"PAKUSARI",
"JATIAN",
"SUBO",
"SUMBERPINANG",
"BEDADUNG",
"PATEMON"
],
"PANTI": [
"KEMUNINGSARI LOR",
"GLAGAHWERO",
"SERUT",
"PANTI",
"PAKIS",
"SUCI",
"KEMIRI"
],
"PATRANG": [
"GEBANG",
"JEMBER LOR",
"PATRANG",
"BARATAN",
"BINTORO",
"SLAWU",
"JUMERTO",
"BANJARSENGON"
],
"PUGER": [
"MOJOMULYO",
"MOJOSARI",
"PUGER KULON",
"PUGER WETAN",
"GRENDEN",
"MLOKOREJO",
"KASIYAN",
"KASIYAN TIMUR",
"WONOSARI",
"JAMBEARUM",
"BAGON",
"WRINGIN TELU"
]
};
document.addEventListener('DOMContentLoaded', () => {
lucide.createIcons();
initWilayahDropdown();
const searchInput = document.getElementById('searchInput');
const hiddenSearch = document.getElementById('hiddenSearch');
searchInput.addEventListener('input', () => {
hiddenSearch.value = searchInput.value;
});
});
function initWilayahDropdown() {
const kecamatan =
document.getElementById('form_kecamatan');
const desa = document.getElementById('form_desa');
const dusun = document.getElementById('form_dusun');
kecamatan.innerHTML =
'<option value="">Pilih Kecamatan</option>';
Object.keys(wilayahJember)
.sort()
.forEach(item => {
kecamatan.innerHTML +=
`<option value="${item}">
${item}
</option>`;
});
kecamatan.addEventListener('change', function() {
desa.innerHTML = '<option value="">Pilih Desa</option>';
dusun.value = '';
if (!this.value) {
desa.disabled = true;
return;
}
Object.keys(wilayahJember[this.value]).forEach(item => {
desa.innerHTML += `
<option value="${item}">
${item}
</option>
`;
});
desa.disabled = false;
desa.addEventListener('change', function() {
dusun.value = '';
});
});
}
function toggleFilter() {
const content = document.getElementById('filterContent');
const icon = document.getElementById('filterIcon');
content.classList.toggle('active');
icon.style.transform = content.classList.contains('active') ? 'rotate(180deg)' : 'rotate(0deg)';
}
function submitExport() {
const f = document.getElementById('filterForm');
const originalAction = f.action;
f.action = "{{ route('pasien.export') }}";
f.submit();
f.action = originalAction;
}
function updateStatusPengobatan(status) {
const btn = document.getElementById('btnTambahPengobatan');
const note = document.getElementById('notePengobatan');
if (!btn || !note) return;
// default = boleh tambah
btn.disabled = false;
btn.style.opacity = '1';
btn.style.cursor = 'pointer';
btn.style.background = '#c40000';
note.style.display = 'none';
note.innerHTML = '';
// HANYA status berikut yang tidak boleh tambah pengobatan
if (
status === 'Masih Proses' ||
status === 'Gagal' ||
status === 'Meninggal'
) {
btn.disabled = true;
btn.style.opacity = '0.55';
btn.style.cursor = 'not-allowed';
btn.style.background = '#9ca3af';
note.style.display = 'block';
if (status === 'Masih Proses') {
note.innerHTML =
'pasien masih menjalani pengobatan aktif.';
} else if (status === 'Gagal') {
note.innerHTML =
'Pasien gagal pengobatan.';
} else if (status === 'Meninggal') {
note.innerHTML =
'Pasien telah meninggal.';
}
}
}
function handleStatusPengobatanChange() {
const semuaStatus =
document.querySelectorAll('.status-pengobatan-select');
if (semuaStatus.length === 0) return;
// ambil status pengobatan TERAKHIR
const statusTerakhir =
semuaStatus[semuaStatus.length - 1].value;
updateStatusPengobatan(statusTerakhir);
}
function updateUploadTahap(parent) {
const status =
parent.querySelector(
'.status-pengobatan-select'
)?.value;
const mulai =
parent.querySelector(
'.tgl-mulai-input'
)?.value;
if (!mulai) return;
const hari =
Math.floor(
(
new Date() -
new Date(mulai)
) / 86400000
);
const fileAwal =
parent.querySelector('.file-awal');
const file56 =
parent.querySelector('.file-56');
const file168 =
parent.querySelector('.file-168');
if (
status === 'Gagal' ||
status === 'Meninggal'
) {
fileAwal.disabled = true;
file56.disabled = true;
file168.disabled = true;
return;
}
fileAwal.disabled = false;
file56.disabled =
hari < 56;
file168.disabled =
hari < 168;
}
function prepareEdit(p) {
if (typeof p === "string") {
p = JSON.parse(p);
} else {
p = JSON.parse(JSON.stringify(p));
}
form.reset();
document.getElementById("form_desa").disabled = true;
document.getElementById("form_dusun").value = "";
document.getElementById("form_desa").innerHTML =
'<option value="">Pilih Desa</option>';
document.getElementById('containerRiwayatPengobatan').innerHTML = '';
document.getElementById('modalTitle').innerText =
'Edit Data Pasien';
document.getElementById('btnSubmitForm').innerText =
'Update Pasien';
document.getElementById('form_mode').value = 'edit';
document.getElementById('btnTambahPengobatan').style.display = 'inline-block';
document.getElementById('form_nama').value =
p.nama_lengkap || '';
document.getElementById('form_nik').value =
p.nik || '';
document.getElementById('form_telp').value =
p.no_telp || '';
document.getElementById('form_tgl_lahir').value =
p.tgl_lahir || '';
document.getElementById('form_jk').value =
p.jenis_kelamin || '';
document.getElementById('form_email').value =
p.email || '';
document.getElementById('rtrw_input').value =
(p.rt_rw || '').replace('RT/RW ', '');
@if(session('role') == 'super_admin')
document.getElementById('form_fasyankes').value =
p.nama_fasyankes || '';
@endif
if (p.alamat) {
const bagian = p.alamat.split(', ');
const dusunValue =
bagian[0] || '';
const desaValue =
bagian[2] ?
bagian[2].replace(/^Desa\s+/i, '') :
'';
const kecamatanValue =
bagian[3] ?
bagian[3].replace(/^Kecamatan\s+/i, '') :
'';
setAlamatEdit(
kecamatanValue,
desaValue,
dusunValue
);
}
// =========================
// AMBIL DATA MEDIS
// =========================
fetch(`/pasien/${p.id_user}/riwayat-pengobatan`)
.then(res => res.json())
.then(res => {
const container =
document.getElementById('containerRiwayatPengobatan');
container.innerHTML = '';
if (
res.success &&
Array.isArray(res.data) &&
res.data.length > 0
) {
res.data.forEach(item => {
item.mode = 'edit';
tambahFormPengobatan(item);
});
const semuaStatus =
document.querySelectorAll('.status-pengobatan-select');
if (semuaStatus.length > 0) {
const statusTerakhir =
semuaStatus[semuaStatus.length - 1].value;
updateStatusPengobatan(statusTerakhir);
}
// ======================================
// PAKSA SEMUA RULE UPLOAD DIEKSEKUSI
// ======================================
setTimeout(() => {
document
.querySelectorAll('.status-pengobatan-select')
.forEach(select => {
handleStatusUpload(select);
});
const terakhir =
document.querySelectorAll(
'.status-pengobatan-select'
);
if (terakhir.length > 0) {
updateStatusPengobatan(
terakhir[terakhir.length - 1].value
);
}
}, 100);
}
})
.catch(error => {
console.error(error);
});
form.action =
"/update-pasien/" + p.id_user;
modal.style.display = 'flex';
lucide.createIcons();
}
function setAlamatEdit(
kecamatan,
desa,
dusun
) {
document.getElementById(
'form_kecamatan'
).value = kecamatan;
document.getElementById(
'form_kecamatan'
).dispatchEvent(
new Event('change')
);
setTimeout(() => {
document.getElementById(
'form_desa'
).value = desa;
document.getElementById(
'form_desa'
).dispatchEvent(
new Event('change')
);
setTimeout(() => {
document.getElementById(
'form_dusun'
).value = dusun;
}, 50);
}, 50);
}
function closeModal() {
modal.style.display = "none";
form.reset();
document.getElementById("containerRiwayatPengobatan").innerHTML = "";
document.getElementById("form_desa").disabled = true;
document.getElementById("form_dusun").value = "";
}
function openModalTambah() {
form.reset();
document.getElementById('form_desa').disabled = true;
document.getElementById('form_dusun').value = '';
document.getElementById('containerRiwayatPengobatan').innerHTML = '';
document.getElementById('modalTitle').innerText =
'Tambah Pasien Baru';
document.getElementById('btnSubmitForm').innerText =
'Simpan Pasien';
document.getElementById('form_mode').value = 'create';
document.getElementById('btnTambahPengobatan').style.display = 'none';
document.getElementById('form_id_user').value = '';
form.action = "/tambah-pasien";
tambahFormPengobatan({
mode: 'create'
});
const note =
document.getElementById('notePengobatan');
if (note) {
note.style.display = 'none';
note.innerHTML = '';
}
modal.style.display = 'flex';
lucide.createIcons();
}
function hitungTanggalSelesai() {
const tglMulai = document.getElementById('form_mulai').value;
if (tglMulai) {
const date = new Date(tglMulai);
date.setDate(date.getDate() + 167);
document.getElementById('form_selesai').value = date.toISOString().split('T')[0];
}
}
// Helper: Kapitalisasi & RT/RW
document.querySelectorAll('input[type="text"]').forEach(input => {
input.addEventListener('input', function() {
let pos = this.selectionStart;
this.value = this.value.toUpperCase();
this.setSelectionRange(pos, pos);
});
});
document.getElementById('rtrw_input').addEventListener('input', function(e) {
let val = e.target.value.replace(/\D/g, '');
if (val.length > 2) val = val.slice(0, 2) + '/' + val.slice(2, 4);
e.target.value = val;
});
function updateStatus(selectElement, id) {
const statusValue = selectElement.value;
const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
selectElement.disabled = true;
fetch(`/update-status/${id}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': token,
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({
status_kesehatan: statusValue
})
})
.then(async response => {
const data = await response.json();
if (!response.ok) throw new Error(data.message || 'Gagal update');
return data;
})
.then(() => {
selectElement.disabled = false;
Swal.fire({
toast: true,
position: 'top-end',
icon: 'success',
title: 'Status diperbarui',
showConfirmButton: false,
timer: 2000
});
})
.catch(error => {
selectElement.disabled = false;
Swal.fire('Error!', error.message, 'error');
});
}
document.getElementById('form_nik').addEventListener('input', function() {
this.value = this.value.replace(/\D/g, '');
const error = document.getElementById('nikError');
const btn = document.getElementById('btnSubmitForm');
if (this.value.length !== 16 && this.value.length > 0) {
error.style.display = 'block';
btn.disabled = true;
} else {
error.style.display = 'none';
btn.disabled = false;
}
});
let searchTimeout;
document.getElementById('searchInput').addEventListener('keyup', function() {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
const searchValue = this.value;
const url = new URL(window.location.href);
if (searchValue) {
url.searchParams.set('search', searchValue);
} else {
url.searchParams.delete('search');
}
window.location.href = url.toString();
}, 500);
});
function aksiHapus(btn, id) {
Swal.fire({
icon: 'warning',
title: 'Hapus Pasien?',
html: `
<div style="margin-top:5px;">
Data pasien akan dihapus permanen dari sistem dan tidak dapat dikembalikan lagi.
</div>
`,
width: '360px',
showCancelButton: true,
confirmButtonText: `
<div style="display:flex;align-items:center;justify-content:center;gap:6px;">
<i data-lucide="trash-2" style="width:16px;height:16px;"></i>
<span>Hapus</span>
</div>
`,
cancelButtonText: `
<div style="display:flex;align-items:center;justify-content:center;gap:6px;">
<i data-lucide="x" style="width:16px;height:16px;"></i>
<span>Batal</span>
</div>
`,
reverseButtons: true,
focusCancel: true,
allowOutsideClick: false,
customClass: {
popup: 'modern-popup',
title: 'title-swal',
confirmButton: 'btn-confirm-swal',
cancelButton: 'btn-cancel-swal'
},
didOpen: () => {
lucide.createIcons();
}
}).then((result) => {
if (result.isConfirmed) {
fetch(`/hapus-pasien/${id}`, {
method: 'DELETE',
headers: {
'X-CSRF-TOKEN': document
.querySelector('meta[name="csrf-token"]')
.getAttribute('content')
}
})
.then(() => {
btn.closest('tr').remove();
refreshIcons();
Swal.fire({
icon: 'success',
title: 'Berhasil Dihapus',
html: `
<div style="margin-top:5px;">
Data pasien berhasil dihapus dari sistem.
</div>
`,
width: '340px',
timer: 1800,
showConfirmButton: false,
customClass: {
popup: 'modern-popup',
title: 'title-swal'
}
});
})
.catch(() => {
Swal.fire({
icon: 'error',
title: 'Gagal',
html: `
<div style="margin-top:5px;">
Terjadi kesalahan saat menghapus data pasien.
</div>
`,
width: '340px',
confirmButtonText: 'Tutup',
customClass: {
popup: 'modern-popup',
title: 'title-swal',
confirmButton: 'btn-confirm-swal'
}
});
});
}
});
}
function generateRiwayatForm(index, data = {}) {
/*
==========================================================
STATUS & FILE
==========================================================
*/
const statusPengobatan =
data.status_kesehatan ?? 'Masih Proses';
const jumlahFile =
parseInt(data.jumlah_file ?? 0);
let uploadDikunci = false;
let warningUpload = '';
const tglMulai =
data.tgl_mulai_pengobatan ?
new Date(data.tgl_mulai_pengobatan) :
null;
const hariIni = new Date();
let bulanBerjalan = 0;
if (tglMulai) {
bulanBerjalan =
(hariIni.getFullYear() - tglMulai.getFullYear()) * 12 +
(hariIni.getMonth() - tglMulai.getMonth());
}
// gagal
if (statusPengobatan === 'Gagal') {
uploadDikunci = true;
warningUpload =
'Upload file tidak tersedia karena pasien gagal pengobatan.';
}
// meninggal
else if (statusPengobatan === 'Meninggal') {
uploadDikunci = true;
warningUpload =
'Upload file tidak tersedia karena pasien telah meninggal.';
}
// upload pertama sudah ada
else if (jumlahFile === 1) {
if (bulanBerjalan < 2) {
uploadDikunci = true;
warningUpload =
'Upload berikutnya dapat dilakukan setelah pasien menjalani pengobatan 2 bulan.';
}
}
// upload kedua sudah ada
else if (jumlahFile === 2) {
if (bulanBerjalan < 5) {
uploadDikunci = true;
warningUpload =
'Upload berikutnya dapat dilakukan setelah pasien menjalani pengobatan 5 bulan.';
}
}
// sudah 3 file
else if (jumlahFile >= 3) {
uploadDikunci = true;
warningUpload =
'Upload file sudah mencapai batas maksimal 3 kali.';
}
return `
<div class="riwayat-item"
style="
border:1px solid #eee;
border-radius:16px;
padding:20px;
margin-bottom:20px;
background:#fafafa;
">
<div style="
display:flex;
justify-content:space-between;
align-items:center;
margin-bottom:15px;
">
<h4 style="color:#111827;">
Pengobatan Ke ${index + 1}
</h4>
<button type="button"
class="btn-hapus-riwayat"
onclick="hapusRiwayat(this)"
style="
background:#fee2e2;
color:#c40000;
border:none;
padding:8px 12px;
border-radius:8px;
cursor:pointer;
font-weight:600;
">
Hapus
</button>
</div>
<input type="hidden"
name="riwayat[${index}][id_medis]"
value="${data.id_medis ?? ''}">
<input type="hidden"
class="jumlah-file"
value="${jumlahFile}">
<div class="form-row">
<div class="form-group">
<label>Diagnosa</label>
<select name="riwayat[${index}][diagnosa]" required>
<option value="TBC Paru"
${data.diagnosa == 'TBC Paru' ? 'selected' : ''}>
TBC Paru
</option>
<option value="TBC Ekstra Paru"
${data.diagnosa == 'TBC Ekstra Paru' ? 'selected' : ''}>
TBC Ekstra Paru
</option>
</select>
</div>
<div class="form-group">
<label>Jam Minum Obat</label>
<input type="time"
name="riwayat[${index}][jam_minum]"
value="${data.jam_minum ?? ''}" required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Tanggal Mulai Pengobatan</label>
<input type="date"
name="riwayat[${index}][tgl_mulai]" required
max="${today}"
value="${data.tgl_mulai_pengobatan ?? ''}"
onchange="hitungSelesaiRiwayat(this)">
</div>
<div class="form-group">
<label>Tanggal Selesai</label>
<input type="date"
name="riwayat[${index}][tgl_selesai]" required
value="${data.tgl_estimasi_selesai ?? ''}"
readonly
style="background:#f3f4f6;">
</div>
<div class="form-group">
<label>Status Pengobatan</label>
<select
name="riwayat[${index}][status_kesehatan]" required
class="status-pengobatan-select"
onchange="validasiStatusSembuh(this)">
<option value="Masih Proses"
${(data.status_kesehatan ?? 'Masih Proses') == 'Masih Proses' ? 'selected' : ''}>
Masih Proses
</option>
<option value="Sembuh"
${data.status_kesehatan == 'Sembuh' ? 'selected' : ''}>
Sembuh
</option>
<option value="Gagal"
${data.status_kesehatan == 'Gagal' ? 'selected' : ''}>
Gagal
</option>
<option value="Meninggal"
${data.status_kesehatan == 'Meninggal' ? 'selected' : ''}>
Meninggal
</option>
</select>
</div>
</div>
<!-- ========================= -->
<!-- HASIL LAB -->
<!-- ========================= -->
<div style="
margin-top:25px;
border-top:1px dashed #d1d5db;
padding-top:20px;
">
${data.mode === 'create' ? `
<div class="form-group">
<label>File Hasil Laboratorium <span style="color:red">*</span></label>
<input
type="file"
class="file-awal file-pasien-input"
name="riwayat[${index}][file_awal]"
accept=".pdf,.jpg,.jpeg,.png"
required>
<small class="status-file-awal"></small>
</div>
` : `
<div class="form-group">
<label>File Sebelum Pengobatan</label>
<input
type="file"
class="file-awal file-pasien-input"
name="riwayat[${index}][file_awal]"
accept=".pdf,.jpg,.jpeg,.png">
<small class="status-file-awal"></small>
</div>
<div class="form-group">
<label>File Setelah 56 Hari</label>
<input
type="file"
class="file-56 file-pasien-input"
name="riwayat[${index}][file_56_hari]"
accept=".pdf,.jpg,.jpeg,.png">
<small class="status-file-56"></small>
</div>
<div class="form-group">
<label>File Setelah 168 Hari</label>
<input
type="file"
class="file-168 file-pasien-input"
name="riwayat[${index}][file_168_hari]"
accept=".pdf,.jpg,.jpeg,.png">
<small class="status-file-168"></small>
</div>
`}
</div>
<input
type="hidden"
class="jumlah-file"
value="${data?.jumlah_file ?? 0}">
</div>
</div>
`;
}
function validasiStatusSembuh(selectElement) {
const parent =
selectElement.closest('.riwayat-item, .pengobatan-item');
const tglMulaiInput =
parent?.querySelector('.tgl-mulai-input');
const status =
selectElement.value;
/*
==========================================
VALIDASI SAAT USER MEMILIH SEMBUH
==========================================
*/
if (
status === 'Sembuh' &&
tglMulaiInput &&
tglMulaiInput.value
) {
const mulai =
new Date(tglMulaiInput.value);
const sekarang =
new Date();
const hari =
Math.floor(
(sekarang - mulai) / 86400000
);
if (hari < 168) {
Swal.fire({
icon: 'warning',
title: 'Tidak Dapat Mengubah Status',
text: 'Tidak bisa mengganti status menjadi sembuh karena pasien masih belum selesai menjalani pengobatan.',
width: '420px',
customClass: {
popup: 'modern-popup',
title: 'title-swal',
confirmButton: 'btn-confirm-swal'
}
});
selectElement.value = 'Masih Proses';
}
}
handleStatusUpload(selectElement);
}
function tambahRiwayatPengobatan(data = {}) {
const container =
document.getElementById('containerRiwayatPengobatan');
const index =
container.querySelectorAll('.riwayat-item').length;
container.insertAdjacentHTML(
'beforeend',
generateRiwayatForm(index, data)
);
updateButtonHapusRiwayat();
lucide.createIcons();
setTimeout(() => {
const wrapper =
container.lastElementChild;
if (wrapper) {
updateUploadTahap(wrapper);
}
}, 100);
}
function hapusRiwayat(btn) {
btn.closest('.riwayat-item').remove();
resetNomorRiwayat();
updateButtonHapusRiwayat();
}
function resetNomorRiwayat() {
document.querySelectorAll('.riwayat-item')
.forEach((item, index) => {
item.querySelector('h4').innerText =
'Pengobatan Ke ' + (index + 1);
item.querySelectorAll('input, select')
.forEach(el => {
if (el.name.includes('[id_medis]')) {
el.name = `riwayat[${index}][id_medis]`;
}
if (el.name.includes('[diagnosa]')) {
el.name = `riwayat[${index}][diagnosa]`;
}
if (el.name.includes('[jam_minum]')) {
el.name = `riwayat[${index}][jam_minum]`;
}
if (el.name.includes('[tgl_mulai]')) {
el.name = `riwayat[${index}][tgl_mulai]`;
}
if (el.name.includes('[tgl_selesai]')) {
el.name = `riwayat[${index}][tgl_selesai]`;
}
if (el.name.includes('[file_awal]')) {
el.name = `riwayat[${index}][file_awal]`;
}
if (el.name.includes('[file_56_hari]')) {
el.name = `riwayat[${index}][file_56_hari]`;
}
if (el.name.includes('[file_168_hari]')) {
el.name = `riwayat[${index}][file_168_hari]`;
}
});
});
}
function updateButtonHapusRiwayat() {
const items =
document.querySelectorAll('.riwayat-item');
items.forEach(item => {
const btn =
item.querySelector('.btn-hapus-riwayat');
if (btn) {
btn.style.display = 'none';
}
});
if (items.length > 1) {
const lastItem =
items[items.length - 1];
const lastBtn =
lastItem.querySelector('.btn-hapus-riwayat');
if (lastBtn) {
lastBtn.style.display = 'inline-block';
}
}
}
function hitungSelesaiRiwayat(el) {
const mulai = el.value;
if (!mulai) return;
const wrapper = el.closest('.riwayat-item');
const selesai = wrapper.querySelector('input[name*="[tgl_selesai]"]');
if (!selesai) return;
const date = parseDateSafe(mulai);
date.setDate(date.getDate() + 167);
selesai.value = date.toISOString().split('T')[0];
}
</script>
@if(session('success'))
<script>
Swal.fire({
title: 'Berhasil!',
text: "{{ session('success') }}",
icon: 'success',
width: '400px',
customClass: {
popup: 'modern-popup',
title: 'title-swal',
confirmButton: 'btn-confirm-swal'
}
});
</script>
@endif
@if(session('error'))
<script>
Swal.fire({
title: 'Gagal!',
text: "{{ session('error') }}",
icon: 'error',
width: '400px',
customClass: {
popup: 'modern-popup',
title: 'title-swal',
confirmButton: 'btn-confirm-swal'
}
});
</script>
@endif
<script>
// ===============================
// PINDAH FASYANKES
// ===============================
const modalPindah = document.getElementById('modalPindahFasyankes');
function openPindahFasyankes(id, nama, fasyankesLama) {
document.getElementById('pindah_id_user').value = id;
document.getElementById('pindah_nama').value = nama;
document.getElementById('pindah_fasyankes_lama').value = fasyankesLama ?? '-';
document.getElementById('pindah_fasyankes_baru').value = '';
modalPindah.style.display = 'flex';
lucide.createIcons();
}
function closeModalPindah() {
modalPindah.style.display = 'none';
}
document.getElementById('formPindahFasyankes')
.addEventListener('submit', function(e) {
e.preventDefault();
const id_user = document.getElementById('pindah_id_user').value;
const nama_fasyankes = document.getElementById('pindah_fasyankes_baru').value;
if (!nama_fasyankes) {
Swal.fire({
icon: 'warning',
title: 'Pilih Fasyankes',
text: 'Silakan pilih tujuan fasyankes.',
width: '360px',
customClass: {
popup: 'modern-popup',
title: 'title-swal',
confirmButton: 'btn-confirm-swal'
}
});
return;
}
fetch(`/pindah-fasyankes/${id_user}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document
.querySelector('meta[name="csrf-token"]')
.getAttribute('content')
},
body: JSON.stringify({
nama_fasyankes: nama_fasyankes
})
})
.then(res => res.json())
.then(res => {
if (!res.success) {
Swal.fire({
icon: 'error',
title: 'Gagal',
text: res.message || 'Gagal memindahkan pasien.',
width: '360px',
customClass: {
popup: 'modern-popup',
title: 'title-swal',
confirmButton: 'btn-confirm-swal'
}
});
return;
}
Swal.fire({
icon: 'success',
title: 'Berhasil',
text: 'Pasien berhasil dipindahkan.',
timer: 1500,
showConfirmButton: false,
width: '360px',
customClass: {
popup: 'modern-popup',
title: 'title-swal'
}
}).then(() => {
location.reload();
});
})
.catch(() => {
Swal.fire({
icon: 'error',
title: 'Error',
text: 'Terjadi kesalahan server.',
width: '360px',
customClass: {
popup: 'modern-popup',
title: 'title-swal',
confirmButton: 'btn-confirm-swal'
}
});
});
});
// =====================================
// DINAMIS RIWAYAT PENGOBATAN
// =====================================
function tambahFormPengobatan(data = null) {
const container =
document.getElementById('containerRiwayatPengobatan');
const items =
container.querySelectorAll('.pengobatan-item');
const currentIndex = items.length;
const nomorPengobatan = currentIndex + 1;
console.log(
'Jumlah pengobatan:',
currentIndex,
'Nomor:',
nomorPengobatan
);
/*
==========================================================
DATA FILE
==========================================================
*/
const jumlahFile =
data?.jumlah_file ?? 0;
const sudahUploadFile =
jumlahFile > 0;
const html = `
<div class="pengobatan-item"
style="
border:1px solid #eee;
border-radius:16px;
padding:18px;
margin-bottom:18px;
background:#fafafa;
">
<input type="hidden"
name="riwayat[${currentIndex}][id_medis]"
value="${data?.id_medis ?? ''}">
<!-- STATUS FILE -->
<input type="hidden"
class="jumlah-file"
value="${jumlahFile}">
<input type="hidden"
class="sudah-upload-file"
value="${sudahUploadFile ? '1' : '0'}">
<div style="
display:flex;
justify-content:space-between;
align-items:center;
margin-bottom:15px;
">
<h5 style="
font-size:15px;
color:#111;
font-weight:700;
">
Pengobatan Ke ${nomorPengobatan}
</h5>
<button
type="button"
class="btn-hapus-pengobatan"
onclick="hapusPengobatan(this)"
style="
display:none;
background:#fee2e2;
color:#c40000;
border:none;
padding:8px 12px;
border-radius:8px;
cursor:pointer;
font-weight:600;
">
Hapus
</button>
</div>
<div class="form-row">
<div class="form-group">
<label>Diagnosa</label>
<select
name="riwayat[${currentIndex}][diagnosa]" required
class="diagnosa-select">
<option value="TBC Paru">
TBC Paru
</option>
<option value="TBC Ekstra Paru">
TBC Ekstra Paru
</option>
</select>
</div>
<div class="form-group">
<label>Jam Minum Obat</label>
<input type="time"
name="riwayat[${currentIndex}][jam_minum]" required
class="jam-minum-input">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Tanggal Mulai Pengobatan</label>
<input type="date"
name="riwayat[${currentIndex}][tgl_mulai]" required
max="${today}"
class="tgl-mulai-input"
onchange="hitungTanggalSelesaiItem(this)">
</div>
<div class="form-group">
<label>Estimasi Selesai (168 Hari)</label>
<input type="date"
name="riwayat[${currentIndex}][tgl_selesai]" required
class="tgl-selesai-input"
readonly
style="background:#f1f5f9;">
</div>
<div class="form-group">
<label>Status Pengobatan</label>
<select
name="riwayat[${currentIndex}][status_kesehatan]" required
class="status-pengobatan-select"
onchange="validasiStatusSembuh(this)">
<option value="Masih Proses"
${(data?.status_kesehatan ?? 'Masih Proses') == 'Masih Proses' ? 'selected' : ''}>
Masih Proses
</option>
<option value="Sembuh"
${data?.status_kesehatan == 'Sembuh' ? 'selected' : ''}>
Sembuh
</option>
<option value="Gagal"
${data?.status_kesehatan == 'Gagal' ? 'selected' : ''}>
Gagal
</option>
<option value="Meninggal"
${data?.status_kesehatan == 'Meninggal' ? 'selected' : ''}>
Meninggal
</option>
</select>
</div>
</div>
<!-- ========================= -->
<!-- UPLOAD HASIL LAB -->
<!-- ========================= -->
<div style="
margin-top:25px;
border-top:1px dashed #d1d5db;
padding-top:20px;
">
<h5 style="
color:#c40000;
margin-bottom:15px;
font-size:15px;
font-weight:700;
">
Upload Hasil Laboratorium
</h5>
${data?.mode === 'create' ? `
<div class="form-group">
<label>
File Hasil Laboratorium
<span style="color:red">*</span>
</label>
<input
type="file"
class="file-awal file-pasien-input"
name="riwayat[${currentIndex}][file_awal]"
accept=".pdf,.jpg,.jpeg,.png"
required>
</div>
` : `
<div class="form-group">
<label>
File Sebelum Pengobatan
</label>
<div class="upload-wrapper">
<input
type="file"
class="file-awal file-pasien-input"
name="riwayat[${currentIndex}][file_awal]"
data-has-file="${data?.file_awal ? '1' : '0'}"
onchange="konfirmasiUploadUlang(this)">
<input
type="hidden"
name="riwayat[${currentIndex}][file_awal_lama]"
value="${data?.file_awal ?? ''}">
<small class="file-info">
${
data?.file_awal
? 'File hasil laboratorium sudah tersimpan'
: 'Belum ada file'
}
</small>
</div>
</div>
<div class="form-group">
<label>
File Setelah 56 Hari
</label>
<input
type="file"
class="file-56 file-pasien-input"
name="riwayat[${currentIndex}][file_56_hari]"
data-has-file="${data?.file_56_hari ? '1' : '0'}"
onchange="konfirmasiUploadUlang(this)">
<input
type="hidden"
name="riwayat[${currentIndex}][file_56_hari_lama]"
value="${data?.file_56_hari ?? ''}">
<small class="file-info">
${
data?.file_56_hari
? 'File hasil laboratorium sudah tersimpan'
: 'Belum ada file'
}
</small>
</div>
<div class="form-group">
<label>
File Setelah 168 Hari
</label>
<input
type="file"
class="file-168 file-pasien-input"
name="riwayat[${currentIndex}][file_168_hari]"
data-has-file="${data?.file_168_hari ? '1' : '0'}"
onchange="konfirmasiUploadUlang(this)">
<input
type="hidden"
name="riwayat[${currentIndex}][file_168_hari_lama]"
value="${data?.file_168_hari ?? ''}">
<small class="file-info">
${
data?.file_168_hari
? 'File hasil laboratorium sudah tersimpan'
: 'Belum ada file'
}
</small>
</div>
`}
<input
type="hidden"
class="jumlah-file"
value="${data?.jumlah_file ?? 0}">
</div>
<input type="hidden"
class="jumlah-file"
value="${data?.jumlah_file ?? 0}">
</div>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', html);
lucide.createIcons();
if (data) {
const terakhir =
container.lastElementChild;
terakhir.querySelector('.diagnosa-select').value =
data.diagnosa || 'TBC Paru';
terakhir.querySelector('.jam-minum-input').value =
data.jam_minum || '';
terakhir.querySelector('.tgl-mulai-input').value =
data.tgl_mulai_pengobatan || '';
terakhir.querySelector('.tgl-selesai-input').value =
data.tgl_estimasi_selesai || '';
const statusSelect =
terakhir.querySelector('.status-pengobatan-select');
if (statusSelect) {
statusSelect.value =
data.status_kesehatan || 'Masih Proses';
handleStatusUpload(statusSelect);
}
}
updateButtonHapusPengobatan();
}
function hapusPengobatan(button) {
const items =
document.querySelectorAll(
'.pengobatan-item'
);
if (items.length <= 1) {
Swal.fire({
icon: 'warning',
title: 'Tidak Bisa Dihapus',
text: 'Pengobatan pertama wajib ada.'
});
return;
}
button.closest('.pengobatan-item').remove();
updateNomorPengobatan();
updateButtonHapusPengobatan();
}
function updateButtonHapusPengobatan() {
const items =
document.querySelectorAll('.pengobatan-item');
items.forEach((item, index) => {
const btn =
item.querySelector('.btn-hapus-pengobatan');
if (!btn) return;
// Pengobatan pertama tidak boleh dihapus
if (index === 0) {
btn.style.display = 'none';
return;
}
// Hanya pengobatan terakhir yang bisa dihapus
btn.style.display =
index === items.length - 1 ?
'flex' :
'none';
});
}
function updateNomorPengobatan() {
const items = document.querySelectorAll('.pengobatan-item');
items.forEach((item, index) => {
item.querySelector('h5').innerText =
`Pengobatan Ke ${index + 1}`;
// update ulang semua name input
item.querySelectorAll('input, select').forEach(el => {
if (el.name.includes('[id_medis]')) {
el.name = `riwayat[${index}][id_medis]`;
}
if (el.name.includes('[diagnosa]')) {
el.name = `riwayat[${index}][diagnosa]`;
}
if (el.name.includes('[jam_minum]')) {
el.name = `riwayat[${index}][jam_minum]`;
}
if (el.name.includes('[tgl_mulai]')) {
el.name = `riwayat[${index}][tgl_mulai]`;
}
if (el.name.includes('[status_kesehatan]')) {
el.name = `riwayat[${index}][status_kesehatan]`;
}
if (el.name.includes('[file_awal]')) {
el.name = `riwayat[${index}][file_awal]`;
}
if (el.name.includes('[file_56_hari]')) {
el.name = `riwayat[${index}][file_56_hari]`;
}
if (el.name.includes('[file_168_hari]')) {
el.name = `riwayat[${index}][file_168_hari]`;
}
});
});
}
function handleStatusUpload(selectElement) {
const parent =
selectElement.closest('.riwayat-item, .pengobatan-item');
if (!parent) return;
const fileAwal =
parent.querySelector('.file-awal');
const file56 =
parent.querySelector('.file-56');
const file168 =
parent.querySelector('.file-168');
const tglMulaiInput =
parent.querySelector('.tgl-mulai-input');
const status =
selectElement.value;
/*
==========================================
STATUS GAGAL / MENINGGAL
==========================================
*/
if (
status === 'Gagal' ||
status === 'Meninggal'
) {
disableFile(fileAwal);
disableFile(file56);
disableFile(file168);
updateFileInfo(parent, 0);
return;
}
/*
==========================================
BELUM ADA TANGGAL MULAI
==========================================
*/
if (!tglMulaiInput || !tglMulaiInput.value) {
enableFile(fileAwal);
disableFile(file56);
disableFile(file168);
updateFileInfo(parent, 0);
return;
}
const mulai =
new Date(tglMulaiInput.value);
const sekarang =
new Date();
const hari =
Math.floor(
(sekarang - mulai) / 86400000
);
enableFile(fileAwal);
if (hari >= 56) {
enableFile(file56);
} else {
disableFile(file56);
}
if (hari >= 168) {
enableFile(file168);
} else {
disableFile(file168);
}
updateFileInfo(parent, hari);
}
function updateFileInfo(parent, hari) {
const infoAwal =
parent.querySelector('.file-awal')
?.closest('.form-group')
?.querySelector('.file-info');
const info56 =
parent.querySelector('.file-56')
?.closest('.form-group')
?.querySelector('.file-info');
const info168 =
parent.querySelector('.file-168')
?.closest('.form-group')
?.querySelector('.file-info');
const fileAwalLama =
parent.querySelector(
'input[name*="[file_awal_lama]"]'
)?.value;
const file56Lama =
parent.querySelector(
'input[name*="[file_56_hari_lama]"]'
)?.value;
const file168Lama =
parent.querySelector(
'input[name*="[file_168_hari_lama]"]'
)?.value;
/*
==========================================
FILE AWAL
==========================================
*/
if (infoAwal) {
if (fileAwalLama) {
infoAwal.innerText =
'File hasil laboratorium sudah tersimpan';
} else {
infoAwal.innerText =
'File hasil laboratorium';
}
}
/*
==========================================
FILE 56 HARI
==========================================
*/
if (info56) {
if (file56Lama) {
info56.innerText =
'File hasil laboratorium sudah tersimpan';
} else if (hari < 56) {
info56.innerText =
`Belum waktunya upload (menunggu hari ke-56)`;
} else {
info56.innerText =
'File hasil laboratorium';
}
}
/*
==========================================
FILE 168 HARI
==========================================
*/
if (info168) {
if (file168Lama) {
info168.innerText =
'File hasil laboratorium sudah tersimpan';
} else if (hari < 168) {
info168.innerText =
`Belum waktunya upload (menunggu hari ke-168)`;
} else {
info168.innerText =
'File hasil laboratorium';
}
}
}
function konfirmasiUploadUlang(input) {
const hasFile =
input.dataset.hasFile === '1';
if (!hasFile) return;
Swal.fire({
icon: 'warning',
title: 'Upload Ulang File?',
html: `
<div style="margin-top:5px;">
File hasil laboratorium yang lama akan digantikan dengan file baru.
</div>
`,
width: '360px',
showCancelButton: true,
reverseButtons: true,
focusCancel: true,
allowOutsideClick: false,
confirmButtonText: `
<div style="
display:flex;
align-items:center;
justify-content:center;
gap:6px;
">
<i data-lucide="refresh-cw"
style="width:16px;height:16px;">
</i>
<span>Upload Ulang</span>
</div>
`,
cancelButtonText: `
<div style="
display:flex;
align-items:center;
justify-content:center;
gap:6px;
">
<i data-lucide="x"
style="width:16px;height:16px;">
</i>
<span>Batal</span>
</div>
`,
customClass: {
popup: 'modern-popup',
title: 'title-swal',
confirmButton: 'btn-confirm-swal',
cancelButton: 'btn-cancel-swal'
},
didOpen: () => {
lucide.createIcons();
}
}).then(result => {
if (!result.isConfirmed) {
input.value = '';
}
});
}
function enableFile(input) {
if (!input) return;
input.disabled = false;
input.style.background = '';
input.style.cursor = '';
input.style.opacity = '1';
}
function disableFile(input) {
if (!input) return;
input.disabled = true;
input.style.background = '#f3f4f6';
input.style.cursor = 'not-allowed';
input.style.opacity = '0.6';
}
function hitungTanggalSelesaiItem(input) {
const item = input.closest('.pengobatan-item');
const selesaiInput = item.querySelector('.tgl-selesai-input');
if (!input.value) {
selesaiInput.value = '';
return;
}
const date = new Date(input.value);
date.setDate(date.getDate() + 167);
selesaiInput.value =
date.toISOString().split('T')[0];
}
document.addEventListener('change', function(e) {
/*
==========================================================
VALIDASI FILE HASIL LAB
==========================================================
*/
if (e.target.classList.contains('file-pasien-input')) {
const maxSize = 5 * 1024 * 1024; // 5 MB
const file = e.target.files[0];
/*
==========================================================
CARI / BUAT WARNING TEXT
==========================================================
*/
let warningText =
e.target.parentElement.querySelector('.upload-warning');
if (!warningText) {
warningText = document.createElement('small');
warningText.classList.add('upload-warning');
warningText.style.display = 'block';
warningText.style.marginTop = '8px';
warningText.style.fontSize = '13px';
warningText.style.fontWeight = '600';
warningText.style.color = '#dc2626';
e.target.parentElement.appendChild(warningText);
}
/*
==========================================================
RESET WARNING
==========================================================
*/
warningText.innerHTML = '';
/*
==========================================================
VALIDASI FILE
==========================================================
*/
if (file && file.size > maxSize) {
warningText.innerHTML =
'File gagal diupload karena ukuran melebihi batas maksimal 5 MB.';
e.target.value = '';
return;
}
/*
==========================================================
VALIDASI FORMAT FILE
==========================================================
*/
const allowedTypes = [
'application/pdf',
'image/jpeg',
'image/jpg',
'image/png'
];
if (file && !allowedTypes.includes(file.type)) {
warningText.innerHTML =
'Format file tidak didukung. Gunakan PDF, JPG, JPEG, atau PNG.';
e.target.value = '';
return;
}
/*
==========================================================
JIKA VALID
==========================================================
*/
warningText.innerHTML = '';
}
});
/*
==========================================================
VALIDASI FORM WAJIB ISI
==========================================================
*/
document.getElementById('pasienForm')
.addEventListener('submit', function(e) {
const form = this;
let adaKosong = false;
form.querySelectorAll(
'input[required], select[required], textarea[required]'
).forEach(field => {
let kosong = false;
if (field.type === 'file') {
kosong = field.files.length === 0;
} else {
kosong = !field.value.trim();
}
if (!field.disabled && kosong) {
adaKosong = true;
field.style.border =
'2px solid #dc2626';
} else {
field.style.border = '';
}
});
if (adaKosong) {
e.preventDefault();
alert(
'Semua form wajib diisi termasuk upload file hasil laboratorium.'
);
return false;
}
});
document.addEventListener('DOMContentLoaded', function() {
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
});
function refreshIcons() {
if (typeof lucide !== 'undefined') {
lucide.createIcons();
}
}
document.addEventListener("click", function(e) {
const btn = e.target.closest(".btn-edit-pasien");
if (!btn) return;
prepareEdit(btn.dataset.item);
});
</script>
</body>
</html>