feat: patient management system + health landing page
Backend:
- Patient model: name, phone, email, age, weight, height, gender,
blood type, disease history, allergies, medications, notes, category
- PatientVisit model: title, content, prescription, visit type,
visit/next-visit dates, linked to patient (cascade delete)
- HealthRequest model: public form submissions for beauty/health care
- Runtime SQLite migrations for all 3 new tables
- Full CRUD API: /api/patients, /api/patients/{id}/visits,
/api/health-requests (public POST + admin GET/PUT/DELETE)
Admin panel:
- 'پرونده بیماران' page: list, search, filter by category (beauty/health)
- Patient profile page: personal info + medical history + visits timeline
- Add/edit patient modal with all medical fields
- Add visit modal: type, date, clinical notes, prescription, next visit
- 'درخواستها' page: manage public health requests, mark as handled
- Badge counter for unhandled requests in sidebar
Frontend (SEO):
- New #health-care section with Schema.org MedicalClinic markup
- Two category cards: زیبایی پوست and سلامت عمومی
- Feature lists with checkmarks per category
- Inline request form that submits to /api/health-request
- Mobile responsive (single column on small screens)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -269,6 +269,15 @@ tr:hover td{background:#FAFBFC}
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||||
گزارش SEO
|
||||
</div>
|
||||
<div class="nav-section">مدیریت بیماران</div>
|
||||
<div class="nav-item" onclick="showPage('patients',this)">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
||||
پرونده بیماران
|
||||
</div>
|
||||
<div class="nav-item" onclick="showPage('healthrequests',this)" id="healthreqNavItem">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 13.6a19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 3.6 3h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L7.91 10.6a16 16 0 0 0 6 6l.91-.91a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>
|
||||
درخواستها <span id="healthreqBadge" style="display:none;background:#E53935;color:#fff;font-size:.65rem;padding:.1rem .4rem;border-radius:50px;margin-right:.3rem"></span>
|
||||
</div>
|
||||
<div class="nav-section">تنظیمات</div>
|
||||
<div class="nav-item" onclick="showPage('security',this)">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
|
||||
@@ -549,6 +558,84 @@ tr:hover td{background:#FAFBFC}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ PATIENTS PAGE ══ -->
|
||||
<div class="page" id="page-patients">
|
||||
<div class="card" style="margin-bottom:1.2rem">
|
||||
<div class="card-header">
|
||||
<div class="card-title">پرونده بیماران</div>
|
||||
<div style="display:flex;gap:.5rem;align-items:center;flex-wrap:wrap">
|
||||
<select id="patientCatFilter" onchange="loadPatients()" style="border:1.5px solid var(--border);border-radius:8px;padding:.4rem .7rem;font-family:inherit;font-size:.82rem;background:var(--white)">
|
||||
<option value="">همه دستهها</option>
|
||||
<option value="beauty">زیبایی پوست</option>
|
||||
<option value="health">سلامت عمومی</option>
|
||||
</select>
|
||||
<input id="patientSearch" placeholder="جستجو نام / تلفن..." oninput="loadPatients()"
|
||||
style="border:1.5px solid var(--border);border-radius:8px;padding:.4rem .8rem;font-family:inherit;font-size:.82rem;outline:none;width:180px"/>
|
||||
<button class="btn btn-primary btn-sm" onclick="openPatientModal()">+ ثبت بیمار جدید</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead><tr><th>نام</th><th>تلفن</th><th>سن</th><th>جنسیت</th><th>دسته</th><th>ویزیتها</th><th>عملیات</th></tr></thead>
|
||||
<tbody id="patientsTable"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ PATIENT PROFILE (sub-page) ══ -->
|
||||
<div class="page" id="page-patient-profile">
|
||||
<div style="display:flex;align-items:center;gap:.8rem;margin-bottom:1.2rem">
|
||||
<button class="btn btn-secondary btn-sm" onclick="showPage('patients',document.querySelector('.nav-item:has(+[onclick*=patients])'))">← بازگشت</button>
|
||||
<h2 id="profileName" style="font-size:1.1rem;font-weight:700"></h2>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1.2rem">
|
||||
<!-- Profile card -->
|
||||
<div class="card">
|
||||
<div class="card-header"><div class="card-title">اطلاعات شخصی</div>
|
||||
<button class="btn btn-secondary btn-sm" id="editPatientBtn" onclick="">✏️ ویرایش</button>
|
||||
</div>
|
||||
<div class="modal-body" id="profileInfo"></div>
|
||||
</div>
|
||||
<!-- Medical history -->
|
||||
<div class="card">
|
||||
<div class="card-header"><div class="card-title">سابقه پزشکی</div></div>
|
||||
<div class="modal-body" id="profileMedical"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Visits timeline -->
|
||||
<div class="card" style="margin-top:1.2rem">
|
||||
<div class="card-header">
|
||||
<div class="card-title">سوابق ویزیت</div>
|
||||
<button class="btn btn-primary btn-sm" onclick="openVisitModal()">+ ثبت ویزیت جدید</button>
|
||||
</div>
|
||||
<div id="visitsTimeline" style="padding:1.2rem"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ HEALTH REQUESTS PAGE ══ -->
|
||||
<div class="page" id="page-healthrequests">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">درخواستهای مراقبت سلامت</div>
|
||||
<div style="display:flex;gap:.5rem">
|
||||
<select id="reqFilter" onchange="loadHealthRequests()" style="border:1.5px solid var(--border);border-radius:8px;padding:.4rem .7rem;font-family:inherit;font-size:.82rem;background:var(--white)">
|
||||
<option value="">همه</option>
|
||||
<option value="false">بررسی نشده</option>
|
||||
<option value="true">بررسی شده</option>
|
||||
</select>
|
||||
<button class="btn btn-secondary btn-sm" onclick="loadHealthRequests()">🔄 بازخوانی</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead><tr><th>نام</th><th>تلفن</th><th>دسته</th><th>پیام</th><th>تاریخ</th><th>وضعیت</th><th>عملیات</th></tr></thead>
|
||||
<tbody id="healthreqTable"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ══ COMMENTS PAGE ══ -->
|
||||
<div class="page" id="page-comments">
|
||||
<div class="card">
|
||||
@@ -640,6 +727,71 @@ tr:hover td{background:#FAFBFC}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Patient Modal -->
|
||||
<div class="modal-overlay hidden" id="patientModal">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title" id="patientModalTitle">ثبت بیمار جدید</div>
|
||||
<button class="modal-close" onclick="closeModal('patientModal')">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="pt-id"/>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:.8rem">
|
||||
<div class="form-group"><label>نام و نام خانوادگی *</label><input id="pt-name" placeholder="نام کامل"/></div>
|
||||
<div class="form-group"><label>شماره تلفن *</label><input id="pt-phone" dir="ltr" placeholder="09xx"/></div>
|
||||
<div class="form-group"><label>ایمیل</label><input id="pt-email" dir="ltr" placeholder="email@example.com"/></div>
|
||||
<div class="form-group"><label>سن</label><input id="pt-age" type="number" min="0" max="120" placeholder="سال"/></div>
|
||||
<div class="form-group"><label>وزن (kg)</label><input id="pt-weight" type="number" step="0.1" placeholder="kg"/></div>
|
||||
<div class="form-group"><label>قد (cm)</label><input id="pt-height" type="number" step="0.1" placeholder="cm"/></div>
|
||||
<div class="form-group"><label>جنسیت</label>
|
||||
<select id="pt-gender"><option value="">انتخاب کنید</option><option value="زن">زن</option><option value="مرد">مرد</option></select>
|
||||
</div>
|
||||
<div class="form-group"><label>گروه خونی</label>
|
||||
<select id="pt-blood"><option value="">نامشخص</option><option>A+</option><option>A-</option><option>B+</option><option>B-</option><option>AB+</option><option>AB-</option><option>O+</option><option>O-</option></select>
|
||||
</div>
|
||||
<div class="form-group"><label>دسته درمانی</label>
|
||||
<select id="pt-cat"><option value="beauty">زیبایی پوست</option><option value="health">سلامت عمومی</option></select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group"><label>سابقه بیماری</label><textarea id="pt-history" rows="3" placeholder="دیابت، فشار خون، بیماری قلبی و ..."></textarea></div>
|
||||
<div class="form-group"><label>حساسیتها</label><textarea id="pt-allergy" rows="2" placeholder="حساسیت به دارو یا مواد غذایی ..."></textarea></div>
|
||||
<div class="form-group"><label>داروهای جاری</label><textarea id="pt-meds" rows="2" placeholder="داروهایی که بیمار مصرف میکند ..."></textarea></div>
|
||||
<div class="form-group"><label>یادداشت کلی</label><textarea id="pt-notes" rows="2" placeholder="نکات مهم ..."></textarea></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" onclick="savePatient()">ذخیره</button>
|
||||
<button class="btn btn-secondary" onclick="closeModal('patientModal')">انصراف</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Visit Modal -->
|
||||
<div class="modal-overlay hidden" id="visitModal">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">ثبت ویزیت / یادداشت پزشکی</div>
|
||||
<button class="modal-close" onclick="closeModal('visitModal')">✕</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<input type="hidden" id="vt-patientId"/>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:.8rem">
|
||||
<div class="form-group"><label>عنوان ویزیت</label><input id="vt-title" placeholder="مثال: ویزیت اول، جلسه لیزر ۲ ..."/></div>
|
||||
<div class="form-group"><label>نوع</label>
|
||||
<select id="vt-type"><option>ویزیت</option><option>آزمایش</option><option>پروسیجر</option><option>مشاوره</option><option>پیگیری</option></select>
|
||||
</div>
|
||||
<div class="form-group"><label>تاریخ ویزیت</label><input id="vt-date" type="datetime-local"/></div>
|
||||
<div class="form-group"><label>ویزیت بعدی</label><input id="vt-next" type="date" placeholder="اختیاری"/></div>
|
||||
</div>
|
||||
<div class="form-group"><label>شرح حال / یافتهها</label><textarea id="vt-content" rows="4" placeholder="معاینه، شکایت بیمار، یافتههای بالینی ..."></textarea></div>
|
||||
<div class="form-group"><label>تجویز / دستورالعمل</label><textarea id="vt-rx" rows="3" placeholder="دارو، دوز، توصیهها ..."></textarea></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-primary" onclick="saveVisit()">ذخیره ویزیت</button>
|
||||
<button class="btn btn-secondary" onclick="closeModal('visitModal')">انصراف</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Gallery Modal -->
|
||||
<div class="modal-overlay hidden" id="galleryModal">
|
||||
<div class="modal">
|
||||
@@ -889,7 +1041,7 @@ function toast(msg, type='success') {
|
||||
}
|
||||
|
||||
// ── Navigation ────────────────────────────────────────────────────────────────
|
||||
const pageTitles = {dashboard:'داشبورد',hero:'صفحه اصلی',about:'درباره من',contact:'تماس',services:'خدمات',gallery:'گالری',testimonials:'نظرات',blogposts:'مقالات',categories:'دستهبندیها',faqs:'سوالات متداول',seo:'گزارش SEO',comments:'مدیریت نظرات',security:'تغییر رمز عبور'};
|
||||
const pageTitles = {dashboard:'داشبورد',hero:'صفحه اصلی',about:'درباره من',contact:'تماس',services:'خدمات',gallery:'گالری',testimonials:'نظرات',blogposts:'مقالات',categories:'دستهبندیها',faqs:'سوالات متداول',seo:'گزارش SEO',comments:'مدیریت نظرات',security:'تغییر رمز عبور',patients:'پرونده بیماران','patient-profile':'پرونده بیمار',healthrequests:'درخواستهای سلامت'};
|
||||
|
||||
function showPage(name, el) {
|
||||
document.querySelectorAll('.page').forEach(p=>p.classList.remove('active'));
|
||||
@@ -913,8 +1065,180 @@ function loadPage(name) {
|
||||
else if (name==='faqs') loadFaqs();
|
||||
else if (name==='seo') loadSeo();
|
||||
else if (name==='comments') loadComments();
|
||||
else if (name==='patients') loadPatients();
|
||||
else if (name==='healthrequests') loadHealthRequests();
|
||||
}
|
||||
|
||||
// ── Patients ──────────────────────────────────────────────────────────────────
|
||||
let patients = [], currentPatientId = null;
|
||||
|
||||
async function loadPatients() {
|
||||
const cat = document.getElementById('patientCatFilter').value;
|
||||
const search = document.getElementById('patientSearch').value;
|
||||
const params = new URLSearchParams();
|
||||
if (cat) params.set('category', cat);
|
||||
if (search) params.set('search', search);
|
||||
patients = await api('/api/patients?' + params) || [];
|
||||
const catLabel = {beauty:'زیبایی پوست', health:'سلامت عمومی'};
|
||||
document.getElementById('patientsTable').innerHTML = patients.map(p => `
|
||||
<tr>
|
||||
<td><strong>${p.fullName}</strong></td>
|
||||
<td dir="ltr">${p.phoneNumber}</td>
|
||||
<td>${p.age || '-'}</td>
|
||||
<td>${p.gender || '-'}</td>
|
||||
<td><span style="background:${p.category==='health'?'#E3F2FD':'#FCE4EC'};color:${p.category==='health'?'#1565C0':'#880E4F'};padding:2px 8px;border-radius:20px;font-size:.72rem">${catLabel[p.category]||p.category}</span></td>
|
||||
<td>${p.visitCount}</td>
|
||||
<td>
|
||||
<button class="btn btn-secondary btn-sm" onclick="openProfile(${p.id})">پرونده</button>
|
||||
<button class="btn btn-secondary btn-sm" onclick="editPatient(${p.id})">ویرایش</button>
|
||||
<button class="btn btn-danger btn-sm" onclick="deletePatient(${p.id})">حذف</button>
|
||||
</td>
|
||||
</tr>`).join('') || '<tr><td colspan="7" style="text-align:center;color:var(--light);padding:2rem">بیماری ثبت نشده</td></tr>';
|
||||
}
|
||||
|
||||
function openPatientModal() {
|
||||
['id','name','phone','email','age','weight','height'].forEach(f => document.getElementById(`pt-${f}`).value='');
|
||||
['gender','blood','cat'].forEach(f => document.getElementById(`pt-${f}`).selectedIndex=0);
|
||||
['history','allergy','meds','notes'].forEach(f => document.getElementById(`pt-${f}`).value='');
|
||||
document.getElementById('patientModalTitle').textContent='ثبت بیمار جدید';
|
||||
document.getElementById('patientModal').classList.remove('hidden');
|
||||
}
|
||||
function editPatient(id) {
|
||||
const p = patients.find(x=>x.id===id); if(!p) return;
|
||||
document.getElementById('pt-id').value=p.id;
|
||||
document.getElementById('pt-name').value=p.fullName||'';
|
||||
document.getElementById('pt-phone').value=p.phoneNumber||'';
|
||||
document.getElementById('pt-email').value=p.email||'';
|
||||
document.getElementById('pt-age').value=p.age||'';
|
||||
document.getElementById('pt-weight').value=p.weight||'';
|
||||
document.getElementById('pt-height').value=p.height||'';
|
||||
document.getElementById('pt-gender').value=p.gender||'';
|
||||
document.getElementById('pt-blood').value=p.bloodType||'';
|
||||
document.getElementById('pt-cat').value=p.category||'beauty';
|
||||
document.getElementById('patientModalTitle').textContent='ویرایش بیمار';
|
||||
document.getElementById('patientModal').classList.remove('hidden');
|
||||
}
|
||||
async function savePatient() {
|
||||
const id = document.getElementById('pt-id').value;
|
||||
const body={
|
||||
fullName:document.getElementById('pt-name').value,
|
||||
phoneNumber:document.getElementById('pt-phone').value,
|
||||
email:document.getElementById('pt-email').value,
|
||||
age:parseInt(document.getElementById('pt-age').value)||0,
|
||||
weight:parseFloat(document.getElementById('pt-weight').value)||0,
|
||||
height:parseFloat(document.getElementById('pt-height').value)||0,
|
||||
gender:document.getElementById('pt-gender').value,
|
||||
bloodType:document.getElementById('pt-blood').value,
|
||||
diseaseHistory:document.getElementById('pt-history').value,
|
||||
allergies:document.getElementById('pt-allergy').value,
|
||||
medications:document.getElementById('pt-meds').value,
|
||||
notes:document.getElementById('pt-notes').value,
|
||||
category:document.getElementById('pt-cat').value,
|
||||
isActive:true
|
||||
};
|
||||
if(id) await api(`/api/patients/${id}`,{method:'PUT',body:JSON.stringify(body)});
|
||||
else await api('/api/patients',{method:'POST',body:JSON.stringify(body)});
|
||||
closeModal('patientModal'); toast('ذخیره شد ✓'); loadPatients();
|
||||
}
|
||||
async function deletePatient(id){if(!confirm('حذف بیمار؟'))return;await api(`/api/patients/${id}`,{method:'DELETE'});toast('حذف شد','error');loadPatients();}
|
||||
|
||||
// Patient Profile
|
||||
async function openProfile(id) {
|
||||
currentPatientId = id;
|
||||
const p = await api(`/api/patients/${id}`);
|
||||
if(!p) return;
|
||||
document.getElementById('profileName').textContent = p.fullName + (p.category==='health' ? ' — سلامت عمومی' : ' — زیبایی پوست');
|
||||
document.getElementById('editPatientBtn').onclick = () => editPatient(id);
|
||||
document.getElementById('profileInfo').innerHTML = `
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:.7rem;font-size:.88rem">
|
||||
<div><b>تلفن:</b> <span dir="ltr">${p.phoneNumber||'-'}</span></div>
|
||||
<div><b>ایمیل:</b> ${p.email||'-'}</div>
|
||||
<div><b>سن:</b> ${p.age||'-'}</div>
|
||||
<div><b>جنسیت:</b> ${p.gender||'-'}</div>
|
||||
<div><b>وزن:</b> ${p.weight||'-'} kg</div>
|
||||
<div><b>قد:</b> ${p.height||'-'} cm</div>
|
||||
<div><b>گروه خونی:</b> ${p.bloodType||'-'}</div>
|
||||
<div><b>تاریخ ثبت:</b> ${new Date(p.createdAt).toLocaleDateString('fa-IR')}</div>
|
||||
</div>
|
||||
${p.notes ? `<p style="margin-top:.8rem;color:var(--mid);font-size:.85rem">${p.notes}</p>` : ''}
|
||||
`;
|
||||
document.getElementById('profileMedical').innerHTML = `
|
||||
<div style="font-size:.88rem;line-height:2">
|
||||
<div><b>سابقه بیماری:</b><br><span style="color:var(--mid)">${p.diseaseHistory||'—'}</span></div>
|
||||
<div style="margin-top:.8rem"><b>حساسیتها:</b><br><span style="color:var(--mid)">${p.allergies||'—'}</span></div>
|
||||
<div style="margin-top:.8rem"><b>داروها:</b><br><span style="color:var(--mid)">${p.medications||'—'}</span></div>
|
||||
</div>
|
||||
`;
|
||||
renderVisits(p.visits||[]);
|
||||
showPage('patient-profile', null);
|
||||
}
|
||||
function renderVisits(visits) {
|
||||
const tl = document.getElementById('visitsTimeline');
|
||||
if(!visits.length){tl.innerHTML='<p style="color:var(--light);text-align:center;padding:2rem">ویزیتی ثبت نشده</p>';return;}
|
||||
const typeColors={'ویزیت':'#1976D2','آزمایش':'#388E3C','پروسیجر':'#7B1FA2','مشاوره':'#F57C00','پیگیری':'#0288D1'};
|
||||
tl.innerHTML = visits.map(v => `
|
||||
<div style="border-right:3px solid ${typeColors[v.visitType]||'#999'};padding:.8rem 1rem .8rem 0;margin-bottom:1.2rem;padding-right:1rem">
|
||||
<div style="display:flex;align-items:center;gap:.6rem;margin-bottom:.4rem">
|
||||
<span style="background:${typeColors[v.visitType]||'#999'};color:#fff;padding:2px 10px;border-radius:20px;font-size:.72rem">${v.visitType}</span>
|
||||
<strong style="font-size:.9rem">${v.title}</strong>
|
||||
<span style="color:var(--light);font-size:.78rem;margin-right:auto">${new Date(v.visitDate).toLocaleDateString('fa-IR')}</span>
|
||||
<button class="btn btn-danger btn-sm" style="padding:.2rem .6rem;font-size:.7rem" onclick="deleteVisit(${v.id})">حذف</button>
|
||||
</div>
|
||||
${v.content?`<p style="font-size:.85rem;color:var(--dark);margin:.3rem 0">${v.content}</p>`:''}
|
||||
${v.prescription?`<div style="background:#F8F9FA;border-radius:8px;padding:.5rem .8rem;font-size:.82rem;margin-top:.4rem;color:#333"><b>تجویز:</b> ${v.prescription}</div>`:''}
|
||||
${v.nextVisitDate?`<p style="font-size:.78rem;color:var(--gold);margin-top:.4rem">📅 ویزیت بعدی: ${new Date(v.nextVisitDate).toLocaleDateString('fa-IR')}</p>`:''}
|
||||
</div>`).join('');
|
||||
}
|
||||
function openVisitModal() {
|
||||
document.getElementById('vt-patientId').value=currentPatientId;
|
||||
['title','content','rx'].forEach(f=>document.getElementById(`vt-${f}`).value='');
|
||||
document.getElementById('vt-type').selectedIndex=0;
|
||||
document.getElementById('vt-date').value=new Date().toISOString().slice(0,16);
|
||||
document.getElementById('vt-next').value='';
|
||||
document.getElementById('visitModal').classList.remove('hidden');
|
||||
}
|
||||
async function saveVisit() {
|
||||
const pid = document.getElementById('vt-patientId').value;
|
||||
const body={
|
||||
title:document.getElementById('vt-title').value,
|
||||
content:document.getElementById('vt-content').value,
|
||||
prescription:document.getElementById('vt-rx').value,
|
||||
visitType:document.getElementById('vt-type').value,
|
||||
visitDate:document.getElementById('vt-date').value||new Date().toISOString(),
|
||||
nextVisitDate:document.getElementById('vt-next').value||null
|
||||
};
|
||||
await api(`/api/patients/${pid}/visits`,{method:'POST',body:JSON.stringify(body)});
|
||||
closeModal('visitModal'); toast('ویزیت ثبت شد ✓');
|
||||
openProfile(parseInt(pid));
|
||||
}
|
||||
async function deleteVisit(vid){if(!confirm('حذف ویزیت؟'))return;await api(`/api/patients/visits/${vid}`,{method:'DELETE'});toast('حذف شد','error');openProfile(currentPatientId);}
|
||||
|
||||
// ── Health Requests ───────────────────────────────────────────────────────────
|
||||
async function loadHealthRequests() {
|
||||
const filter = document.getElementById('reqFilter').value;
|
||||
const params = filter !== '' ? `?handled=${filter}` : '';
|
||||
const reqs = await api('/api/health-requests' + params) || [];
|
||||
const pending = reqs.filter(r=>!r.isHandled).length;
|
||||
const badge = document.getElementById('healthreqBadge');
|
||||
if(pending>0){badge.textContent=pending;badge.style.display='inline';}else{badge.style.display='none';}
|
||||
const catLabel = {beauty:'زیبایی پوست', health:'سلامت عمومی'};
|
||||
document.getElementById('healthreqTable').innerHTML = reqs.map(r=>`
|
||||
<tr style="${!r.isHandled?'font-weight:600':'opacity:.7'}">
|
||||
<td>${r.fullName}</td>
|
||||
<td dir="ltr">${r.phoneNumber}</td>
|
||||
<td>${catLabel[r.category]||r.category}</td>
|
||||
<td style="max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${r.message}">${r.message}</td>
|
||||
<td>${new Date(r.createdAt).toLocaleDateString('fa-IR')}</td>
|
||||
<td><span style="background:${r.isHandled?'#E8F5E9':'#FFEBEE'};color:${r.isHandled?'#388E3C':'#C62828'};padding:2px 8px;border-radius:20px;font-size:.72rem">${r.isHandled?'بررسی شده':'جدید'}</span></td>
|
||||
<td>
|
||||
${!r.isHandled?`<button class="btn btn-secondary btn-sm" onclick="handleReq(${r.id})">✓ بررسی شد</button>`:''}
|
||||
<button class="btn btn-danger btn-sm" onclick="deleteReq(${r.id})">حذف</button>
|
||||
</td>
|
||||
</tr>`).join('') || '<tr><td colspan="7" style="text-align:center;color:var(--light);padding:2rem">درخواستی وجود ندارد</td></tr>';
|
||||
}
|
||||
async function handleReq(id){await api(`/api/health-requests/${id}`,{method:'PUT'});toast('علامتگذاری شد ✓');loadHealthRequests();}
|
||||
async function deleteReq(id){if(!confirm('حذف؟'))return;await api(`/api/health-requests/${id}`,{method:'DELETE'});toast('حذف شد','error');loadHealthRequests();}
|
||||
|
||||
// ── Comments ──────────────────────────────────────────────────────────────────
|
||||
async function loadComments() {
|
||||
const all = await api('/api/comments') || [];
|
||||
@@ -1738,6 +2062,7 @@ async function init(){
|
||||
loadDashboard();
|
||||
loadCategories();
|
||||
loadComments(); // populate pending badge on load
|
||||
loadHealthRequests(); // populate health requests badge on load
|
||||
}
|
||||
|
||||
// Auto-login if token exists
|
||||
|
||||
Reference in New Issue
Block a user