fix: 3 bugs — beauty icon color, appointments on dashboard, blog image edit
CI/CD / CI · dotnet build (push) Successful in 1m28s
CI/CD / Deploy · drsousan (push) Has been cancelled

1. Beauty category icon: was pink (#C2185B), now uses site primary gold
   (var(--gold) / var(--gold-pale)) to match brand color

2. Dashboard now shows health requests:
   - Two new stat cards: total patients + pending requests (clickable)
   - 'آخرین درخواست‌ها' mini-table showing last 6 requests
   - Sidebar badge updates from dashboard load too
   - loadDashboard() now fetches /api/patients + /api/health-requests

3. Blog image edit fix:
   - applyCrop() now captures inputId/previewId BEFORE closeCropper()
     to prevent any potential race condition when replacing images

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-02 15:09:46 +03:30
parent 3780dcccf2
commit ed25bec200
2 changed files with 38 additions and 7 deletions
+1 -1
View File
@@ -179,7 +179,7 @@
.health-cat-card { background:var(--white); border-radius:24px; padding:2.2rem; border:1px solid var(--border); display:flex; flex-direction:column; gap:1rem; } .health-cat-card { background:var(--white); border-radius:24px; padding:2.2rem; border:1px solid var(--border); display:flex; flex-direction:column; gap:1rem; }
.health-cat-icon { width:60px; height:60px; border-radius:18px; display:flex; align-items:center; justify-content:center; } .health-cat-icon { width:60px; height:60px; border-radius:18px; display:flex; align-items:center; justify-content:center; }
.health-cat-icon svg { width:28px; height:28px; } .health-cat-icon svg { width:28px; height:28px; }
.health-cat-icon.beauty { background:#FCE4EC; color:#C2185B; } .health-cat-icon.beauty { background:var(--gold-pale); color:var(--gold); }
.health-cat-icon.health { background:#E3F2FD; color:#1565C0; } .health-cat-icon.health { background:#E3F2FD; color:#1565C0; }
.health-cat-card h3 { font-size:1.15rem; font-weight:700; color:var(--dark); } .health-cat-card h3 { font-size:1.15rem; font-weight:700; color:var(--dark); }
.health-cat-card p { font-size:.88rem; color:var(--mid); line-height:1.8; } .health-cat-card p { font-size:.88rem; color:var(--mid); line-height:1.8; }
+37 -6
View File
@@ -310,10 +310,21 @@ tr:hover td{background:#FAFBFC}
<div class="stat-card"><div class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></div><div class="label">کل بازدید مقالات</div><div class="value" id="ds-views">-</div></div> <div class="stat-card"><div class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></div><div class="label">کل بازدید مقالات</div><div class="value" id="ds-views">-</div></div>
<div class="stat-card"><div class="icon"><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"/></svg></div><div class="label">نظرات بیماران</div><div class="value" id="ds-testimonials">-</div></div> <div class="stat-card"><div class="icon"><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"/></svg></div><div class="label">نظرات بیماران</div><div class="value" id="ds-testimonials">-</div></div>
<div class="stat-card"><div class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/></svg></div><div class="label">مقالات بدون متا</div><div class="value" id="ds-nometa" style="color:var(--danger)">-</div></div> <div class="stat-card"><div class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/></svg></div><div class="label">مقالات بدون متا</div><div class="value" id="ds-nometa" style="color:var(--danger)">-</div></div>
<div class="stat-card" onclick="showPage('patients',document.getElementById('patientsNavItem'))" style="cursor:pointer"><div class="icon" style="color:#1565C0"><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="label">تعداد بیماران</div><div class="value" id="ds-patients">-</div></div>
<div class="stat-card" onclick="showPage('healthrequests',document.getElementById('healthreqNavItem'))" style="cursor:pointer"><div class="icon" style="color:#E53935"><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></div><div class="label">درخواست‌های جدید</div><div class="value" id="ds-requests" style="color:var(--danger)">-</div></div>
</div> </div>
<div class="card"> <div style="display:grid;grid-template-columns:1fr 1fr;gap:1.2rem;margin-bottom:1.2rem">
<div class="card-header"><div class="card-title">پربازدیدترین مقالات</div></div> <div class="card">
<div class="table-wrap"><table><thead><tr><th>عنوان</th><th>بازدید</th><th>عملیات</th></tr></thead><tbody id="topPostsTable"></tbody></table></div> <div class="card-header"><div class="card-title">پربازدیدترین مقالات</div></div>
<div class="table-wrap"><table><thead><tr><th>عنوان</th><th>بازدید</th><th>عملیات</th></tr></thead><tbody id="topPostsTable"></tbody></table></div>
</div>
<div class="card">
<div class="card-header">
<div class="card-title">آخرین درخواست‌های سلامت</div>
<button class="btn btn-secondary btn-sm" onclick="showPage('healthrequests',document.getElementById('healthreqNavItem'))">مشاهده همه</button>
</div>
<div class="table-wrap"><table><thead><tr><th>نام</th><th>تلفن</th><th>دسته</th><th>وضعیت</th></tr></thead><tbody id="dashReqTable"></tbody></table></div>
</div>
</div> </div>
</div> </div>
@@ -1313,9 +1324,11 @@ async function sendReply(id) {
// ── Dashboard ───────────────────────────────────────────────────────────────── // ── Dashboard ─────────────────────────────────────────────────────────────────
async function loadDashboard() { async function loadDashboard() {
const [seo, testims] = await Promise.all([ const [seo, testims, patients, reqs] = await Promise.all([
api('/api/seo/stats'), api('/api/seo/stats'),
api('/api/testimonials/all') api('/api/testimonials/all'),
api('/api/patients'),
api('/api/health-requests')
]); ]);
if (seo) { if (seo) {
document.getElementById('ds-posts').textContent = seo.total; document.getElementById('ds-posts').textContent = seo.total;
@@ -1325,6 +1338,22 @@ async function loadDashboard() {
tb.innerHTML = seo.topPosts.map(p=>`<tr><td>${p.title}</td><td><span class="badge badge-gold">${p.viewCount}</span></td><td><a href="/blog/${p.slug}" target="_blank" class="btn btn-secondary btn-sm">مشاهده</a></td></tr>`).join(''); tb.innerHTML = seo.topPosts.map(p=>`<tr><td>${p.title}</td><td><span class="badge badge-gold">${p.viewCount}</span></td><td><a href="/blog/${p.slug}" target="_blank" class="btn btn-secondary btn-sm">مشاهده</a></td></tr>`).join('');
} }
if (testims) document.getElementById('ds-testimonials').textContent = testims.length; if (testims) document.getElementById('ds-testimonials').textContent = testims.length;
if (patients) document.getElementById('ds-patients').textContent = patients.length;
if (reqs) {
const pending = reqs.filter(r=>!r.isHandled);
document.getElementById('ds-requests').textContent = pending.length;
const catLabel = {beauty:'زیبایی پوست', health:'سلامت عمومی'};
document.getElementById('dashReqTable').innerHTML = reqs.slice(0,6).map(r=>`
<tr style="${!r.isHandled?'font-weight:600':'opacity:.65'}">
<td>${r.fullName}</td>
<td dir="ltr" style="font-size:.8rem">${r.phoneNumber}</td>
<td style="font-size:.78rem">${catLabel[r.category]||r.category}</td>
<td><span style="background:${r.isHandled?'#E8F5E9':'#FFEBEE'};color:${r.isHandled?'#388E3C':'#C62828'};padding:2px 8px;border-radius:20px;font-size:.7rem">${r.isHandled?'بررسی شده':'جدید'}</span></td>
</tr>`).join('') || '<tr><td colspan="4" style="text-align:center;color:var(--light);padding:1rem">درخواستی وجود ندارد</td></tr>';
// update sidebar badge
const badge = document.getElementById('healthreqBadge');
if(pending.length>0){badge.textContent=pending.length;badge.style.display='inline';}else{badge.style.display='none';}
}
} }
// ── Section settings ────────────────────────────────────────────────────────── // ── Section settings ──────────────────────────────────────────────────────────
@@ -1624,9 +1653,11 @@ async function applyCrop() {
const out = document.createElement('canvas'); const out = document.createElement('canvas');
out.width = Math.round(sw); out.height = Math.round(sh); out.width = Math.round(sw); out.height = Math.round(sh);
out.getContext('2d').drawImage(cropper.img, sx, sy, sw, sh, 0, 0, out.width, out.height); out.getContext('2d').drawImage(cropper.img, sx, sy, sw, sh, 0, 0, out.width, out.height);
// Capture targets BEFORE closing (closeCropper doesn't clear them, but be safe)
const _inputId = cropper.inputId, _previewId = cropper.previewId;
out.toBlob(async blob => { out.toBlob(async blob => {
closeCropper(); closeCropper();
const inputId = cropper.inputId, previewId = cropper.previewId; const inputId = _inputId, previewId = _previewId;
const btn = document.getElementById(`upload-btn-${inputId}`); const btn = document.getElementById(`upload-btn-${inputId}`);
const orig = btn.innerHTML; const orig = btn.innerHTML;
btn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="animation:spin .8s linear infinite"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg> در حال آپلود...'; btn.innerHTML = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="animation:spin .8s linear infinite"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg> در حال آپلود...';