Files
draletaha/DrSousan.Api/wwwroot/admin/index.html
T
soroush.asadi e79ccf7e8c
CI/CD / CI · dotnet build (push) Successful in 41s
CI/CD / Deploy · drsousan (push) Successful in 29s
feat: logo and favicon management in admin panel
Admin panel:
- New 'هویت سایت' page under تنظیمات in sidebar
- Upload logo (PNG transparent, 200×60px recommended)
- Upload favicon (PNG/ICO, 32×32 or 64×64px)
- Live preview panel shows how logo looks in header
  and how favicon looks in a browser tab mockup
- Saved to SiteSettings with section='identity', key='logo'/'favicon'

Frontend (_Layout.cshtml):
- Injects AppDbContext to load identity settings per request
- If logo is set: shows <img> in header instead of text
- If favicon is set: uses uploaded file as <link rel="icon">
- Falls back to text / favicon.ico when not configured

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-06-02 17:47:49 +03:30

2317 lines
145 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
<title>پنل مدیریت | دکتر سوسن آل‌طه</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700&display=swap" media="print" onload="this.media='all'"/>
<noscript><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700&display=swap"/></noscript>
<style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
:root{
--gold:#B8955A;--gold-l:#D4B483;--gold-pale:#F5ECD8;
--bg:#F4F6F8;--white:#fff;--dark:#1E1E1E;--mid:#5A5A5A;
--light:#9A9A9A;--border:#E2E8F0;--danger:#E53935;--success:#2E7D32;
--sidebar:240px;
}
body{font-family:'Vazirmatn',Tahoma,sans-serif;background:var(--bg);color:var(--dark);direction:rtl;display:flex;min-height:100vh}
/* ── Sidebar ── */
.sidebar{width:var(--sidebar);background:var(--dark);color:#fff;display:flex;flex-direction:column;flex-shrink:0;position:fixed;top:0;right:0;bottom:0;z-index:50}
.sidebar-logo{padding:1.5rem 1.2rem;border-bottom:1px solid rgba(255,255,255,.08);font-size:.9rem;font-weight:600;color:var(--gold-l);line-height:1.4}
.sidebar-logo span{display:block;font-size:.7rem;color:rgba(255,255,255,.4);font-weight:400;margin-top:.2rem}
nav.sidebar-nav{flex:1;overflow-y:auto;padding:.8rem 0}
.nav-section{font-size:.65rem;font-weight:600;letter-spacing:.08em;color:rgba(255,255,255,.3);padding:.8rem 1.2rem .3rem;text-transform:uppercase}
.nav-item{display:flex;align-items:center;gap:.7rem;padding:.65rem 1.2rem;color:rgba(255,255,255,.65);cursor:pointer;font-size:.85rem;transition:background .2s,color .2s;border-right:3px solid transparent}
.nav-item:hover{background:rgba(255,255,255,.05);color:#fff}
.nav-item.active{background:rgba(184,149,90,.12);color:var(--gold-l);border-right-color:var(--gold)}
.nav-item svg{width:16px;height:16px;flex-shrink:0;opacity:.7}
.nav-item.active svg{opacity:1}
.sidebar-footer{padding:1rem 1.2rem;border-top:1px solid rgba(255,255,255,.08);font-size:.75rem;color:rgba(255,255,255,.35)}
/* ── Main ── */
.main{margin-right:var(--sidebar);flex:1;display:flex;flex-direction:column;min-height:100vh}
.topbar{background:var(--white);border-bottom:1px solid var(--border);padding:.75rem 2rem;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:40}
.topbar-title{font-size:1rem;font-weight:600;color:var(--dark)}
.topbar-actions{display:flex;gap:.7rem;align-items:center}
.page-content{padding:2rem;flex:1}
/* ── Cards ── */
.stat-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:1.2rem;margin-bottom:2rem}
.stat-card{background:var(--white);border-radius:14px;padding:1.4rem 1.6rem;border:1px solid var(--border);display:flex;flex-direction:column;gap:.4rem}
.stat-card .label{font-size:.78rem;color:var(--light)}
.stat-card .value{font-size:2rem;font-weight:700;color:var(--dark)}
.stat-card .icon{width:38px;height:38px;border-radius:10px;background:var(--gold-pale);display:flex;align-items:center;justify-content:center;margin-bottom:.4rem}
.stat-card .icon svg{width:18px;height:18px;color:var(--gold)}
.card{background:var(--white);border-radius:14px;border:1px solid var(--border);overflow:hidden;margin-bottom:1.5rem}
.card-header{padding:1.2rem 1.5rem;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between}
.card-title{font-size:.95rem;font-weight:600}
.card-body{padding:1.5rem}
/* ── Buttons ── */
.btn{display:inline-flex;align-items:center;gap:.4rem;padding:.55rem 1.1rem;border-radius:8px;font-family:inherit;font-size:.85rem;font-weight:500;cursor:pointer;border:1.5px solid transparent;transition:all .2s}
.btn-primary{background:var(--gold);color:#fff;border-color:var(--gold)}
.btn-primary:hover{background:var(--gold-l)}
.btn-secondary{background:transparent;color:var(--mid);border-color:var(--border)}
.btn-secondary:hover{border-color:var(--gold);color:var(--gold)}
.btn-danger{background:transparent;color:var(--danger);border-color:var(--danger)}
.btn-danger:hover{background:var(--danger);color:#fff}
.btn-sm{padding:.35rem .7rem;font-size:.78rem}
.btn svg{width:15px;height:15px}
/* ── Table ── */
.table-wrap{overflow-x:auto}
table{width:100%;border-collapse:collapse;font-size:.85rem}
th{background:#F8FAFC;padding:.75rem 1rem;text-align:right;font-weight:600;color:var(--mid);font-size:.78rem;border-bottom:1px solid var(--border)}
td{padding:.75rem 1rem;border-bottom:1px solid var(--border);color:var(--dark);vertical-align:middle}
tr:last-child td{border-bottom:none}
tr:hover td{background:#FAFBFC}
.badge{display:inline-flex;align-items:center;padding:.2rem .65rem;border-radius:50px;font-size:.72rem;font-weight:600}
.badge-green{background:#E8F5E9;color:#2E7D32}
.badge-red{background:#FFEBEE;color:#C62828}
.badge-gold{background:var(--gold-pale);color:var(--gold)}
/* ── Forms ── */
.form-grid{display:grid;gap:1rem}
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:1rem}
.form-group{display:flex;flex-direction:column;gap:.35rem}
.form-group label{font-size:.82rem;font-weight:500;color:var(--dark)}
.form-group input,.form-group select,.form-group textarea{
border:1.5px solid var(--border);border-radius:8px;padding:.65rem .9rem;
font-family:inherit;font-size:.88rem;color:var(--dark);direction:rtl;
transition:border-color .2s,box-shadow .2s;outline:none;background:var(--white)
}
.form-group input:focus,.form-group select:focus,.form-group textarea:focus{border-color:var(--gold);box-shadow:0 0 0 3px rgba(184,149,90,.1)}
.form-group textarea{resize:vertical;min-height:100px}
.form-hint{font-size:.72rem;color:var(--light);margin-top:.2rem}
/* ── Upload input group ── */
.input-upload-wrap{display:flex;gap:.45rem;align-items:center}
.input-upload-wrap input{flex:1;min-width:0}
.upload-btn{flex-shrink:0;display:inline-flex;align-items:center;gap:.3rem;padding:.55rem .8rem;border-radius:8px;border:1.5px dashed var(--gold);background:var(--gold-pale);color:var(--gold);font-family:inherit;font-size:.78rem;font-weight:600;cursor:pointer;transition:all .2s;white-space:nowrap}
.upload-btn:hover{background:var(--gold);color:#fff;border-style:solid}
.upload-btn:disabled{opacity:.5;cursor:not-allowed}
.upload-btn svg{width:14px;height:14px}
.upload-remove{flex-shrink:0;display:inline-flex;align-items:center;gap:.3rem;padding:.55rem .8rem;border-radius:8px;border:1.5px solid var(--border);background:#fff;color:#c0392b;font-family:inherit;font-size:.78rem;font-weight:600;cursor:pointer;transition:all .2s}
.upload-remove:hover{background:#c0392b;color:#fff;border-color:#c0392b}
.upload-preview{display:none;width:100%;max-height:120px;object-fit:cover;border-radius:8px;border:1.5px solid var(--border);margin-top:.4rem;background:var(--section-bg)}
/* ── File Manager button ── */
.fm-open-btn{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border-radius:8px;border:1.5px solid var(--border);background:var(--white);color:var(--mid);cursor:pointer;transition:all .2s;padding:0}
.fm-open-btn:hover{border-color:var(--gold);color:var(--gold);background:var(--gold-pale)}
.fm-open-btn svg{width:15px;height:15px;pointer-events:none}
/* ── File Manager modal ── */
.fm-overlay{position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:300;display:flex;align-items:center;justify-content:center;padding:1rem}
.fm-modal{background:var(--white);border-radius:18px;width:100%;max-width:880px;height:82vh;display:flex;flex-direction:column;box-shadow:0 24px 80px rgba(0,0,0,.22);overflow:hidden}
.fm-head{padding:1rem 1.4rem;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:.8rem;flex-shrink:0}
.fm-head-title{font-size:.95rem;font-weight:600;flex:1}
.fm-search{flex:1;max-width:240px;border:1.5px solid var(--border);border-radius:8px;padding:.45rem .8rem;font-family:inherit;font-size:.83rem;direction:rtl;outline:none;transition:border-color .2s}
.fm-search:focus{border-color:var(--gold)}
.fm-body{flex:1;overflow-y:auto;padding:1.2rem}
.fm-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:.9rem}
.fm-tile{border:2px solid var(--border);border-radius:12px;overflow:hidden;cursor:pointer;transition:border-color .2s,box-shadow .2s;background:var(--bg)}
.fm-tile:hover{border-color:var(--gold);box-shadow:0 4px 18px rgba(184,149,90,.18)}
.fm-tile.fm-selected{border-color:var(--gold);box-shadow:0 0 0 3px rgba(184,149,90,.25)}
.fm-thumb{width:100%;aspect-ratio:1;background-size:cover;background-position:center;background-color:var(--section-bg)}
.fm-info{padding:.5rem .6rem .3rem;border-top:1px solid var(--border)}
.fm-name{display:block;font-size:.7rem;color:var(--dark);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;direction:ltr;text-align:left}
.fm-meta{font-size:.65rem;color:var(--light)}
.fm-actions{display:flex;gap:.35rem;padding:.4rem .6rem .6rem}
.fm-actions .btn{flex:1;padding:.35rem .4rem;font-size:.72rem;justify-content:center}
.fm-empty{text-align:center;padding:4rem 2rem;color:var(--mid);font-size:.9rem}
.fm-loading{text-align:center;padding:4rem 2rem;color:var(--gold);display:flex;flex-direction:column;align-items:center;gap:.8rem;font-size:.85rem}
@keyframes spin{to{transform:rotate(360deg)}}
.seo-score{display:flex;align-items:center;gap:.5rem;padding:.6rem 1rem;border-radius:8px;font-size:.8rem}
.seo-score.good{background:#E8F5E9;color:#2E7D32}
.seo-score.ok{background:#FFF3E0;color:#E65100}
.seo-score.bad{background:#FFEBEE;color:#C62828}
/* ── Service Before/After pair ── */
.ba-pair{display:grid;grid-template-columns:1fr 1fr;gap:.8rem}
.ba-pair .upload-preview{max-height:140px}
/* ── Image Cropper Modal ── */
.cropper-overlay{position:fixed;inset:0;background:rgba(0,0,0,.72);z-index:500;display:flex;align-items:center;justify-content:center;padding:1rem}
.cropper-modal{background:#1a1a1a;border-radius:18px;width:100%;max-width:680px;display:flex;flex-direction:column;box-shadow:0 24px 80px rgba(0,0,0,.5);overflow:hidden;max-height:92vh}
.cropper-head{padding:.85rem 1.2rem;display:flex;align-items:center;gap:.7rem;border-bottom:1px solid #333}
.cropper-head-title{color:#fff;font-size:.95rem;font-weight:600;flex:1}
.cropper-stage{flex:1;overflow:hidden;position:relative;background:#111;cursor:crosshair;user-select:none;min-height:320px}
.cropper-stage canvas{display:block;width:100%;height:100%;object-fit:contain}
.cropper-box{position:absolute;border:2px solid #B8955A;box-shadow:0 0 0 9999px rgba(0,0,0,.52);cursor:move;box-sizing:border-box}
.cropper-handle{position:absolute;width:10px;height:10px;background:#B8955A;border-radius:50%;z-index:2}
.cropper-handle.nw{top:-5px;left:-5px;cursor:nw-resize}
.cropper-handle.ne{top:-5px;right:-5px;cursor:ne-resize}
.cropper-handle.sw{bottom:-5px;left:-5px;cursor:sw-resize}
.cropper-handle.se{bottom:-5px;right:-5px;cursor:se-resize}
.cropper-foot{padding:.85rem 1.2rem;display:flex;gap:.6rem;align-items:center;border-top:1px solid #333;flex-wrap:wrap}
.cropper-hint{color:#888;font-size:.78rem;flex:1}
.cropper-ratio-btns{display:flex;gap:.4rem}
.cropper-ratio-btn{background:#2a2a2a;border:1.5px solid #444;color:#ccc;padding:.35rem .7rem;border-radius:8px;font-size:.75rem;cursor:pointer;transition:all .2s;font-family:inherit}
.cropper-ratio-btn.active,.cropper-ratio-btn:hover{background:#B8955A;border-color:#B8955A;color:#fff}
/* ── Icon Picker ── */
.icon-picker-section{margin-top:.9rem}
.icon-picker-section>label{display:block;font-size:.82rem;font-weight:600;color:var(--mid);margin-bottom:.55rem}
.icon-selected-preview{display:flex;align-items:center;gap:.7rem;padding:.6rem .9rem;border:1.5px solid var(--border);border-radius:8px;background:var(--section-bg);margin-bottom:.65rem;cursor:pointer;transition:border-color .2s}
.icon-selected-preview:hover{border-color:var(--gold)}
.icon-preview-box{width:40px;height:40px;display:flex;align-items:center;justify-content:center;background:var(--gold-pale);border-radius:10px;color:var(--gold);flex-shrink:0}
.icon-preview-box svg{width:22px;height:22px}
.icon-preview-label{font-size:.82rem;color:var(--mid)}
.icon-grid-wrap{border:1.5px solid var(--border);border-radius:10px;overflow:hidden;max-height:0;transition:max-height .3s ease}
.icon-grid-wrap.open{max-height:280px;overflow-y:auto}
.icon-grid{display:grid;grid-template-columns:repeat(8,1fr);gap:0;padding:.5rem}
.icon-opt{display:flex;flex-direction:column;align-items:center;gap:.3rem;padding:.55rem .2rem;border-radius:8px;cursor:pointer;border:2px solid transparent;transition:all .15s;color:var(--mid)}
.icon-opt:hover{background:var(--gold-pale);color:var(--gold)}
.icon-opt.selected{border-color:var(--gold);background:var(--gold-pale);color:var(--gold)}
.icon-opt svg{width:20px;height:20px}
.icon-opt span{font-size:.6rem;text-align:center;line-height:1.2}
/* ── Modal ── */
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:200;display:flex;align-items:center;justify-content:center;padding:1rem}
.modal-overlay.hidden,.fm-overlay.hidden,.cropper-overlay.hidden{display:none}
.modal{background:var(--white);border-radius:16px;width:100%;max-width:720px;max-height:90vh;overflow-y:auto}
.modal-header{padding:1.2rem 1.5rem;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;background:var(--white);z-index:1}
.modal-title{font-size:1rem;font-weight:600}
.modal-close{background:none;border:none;cursor:pointer;padding:.4rem;color:var(--light)}
.modal-body{padding:1.5rem}
.modal-footer{padding:1rem 1.5rem;border-top:1px solid var(--border);display:flex;gap:.7rem;justify-content:flex-start}
/* ── Toast ── */
.toast-container{position:fixed;bottom:1.5rem;left:1.5rem;z-index:300;display:flex;flex-direction:column;gap:.5rem}
.toast{background:var(--dark);color:#fff;padding:.7rem 1.2rem;border-radius:10px;font-size:.85rem;animation:slideIn .3s ease;box-shadow:0 4px 20px rgba(0,0,0,.2)}
.toast.success{border-left:4px solid #4CAF50}
.toast.error{border-left:4px solid var(--danger)}
@keyframes slideIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
/* ── Rich text editor minimal ── */
.editor-toolbar{border:1.5px solid var(--border);border-bottom:none;border-radius:8px 8px 0 0;padding:.4rem .6rem;display:flex;gap:.3rem;flex-wrap:wrap;background:#F8FAFC}
.editor-toolbar button{background:none;border:none;padding:.3rem .5rem;border-radius:4px;cursor:pointer;font-size:.82rem;font-family:inherit;color:var(--mid)}
.editor-toolbar button:hover{background:var(--gold-pale);color:var(--gold)}
.editor-content{border:1.5px solid var(--border);border-radius:0 0 8px 8px;padding:.8rem 1rem;min-height:250px;outline:none;font-size:.9rem;line-height:1.8;direction:rtl}
/* ── Pages ── */
.page{display:none}
.page.active{display:block}
/* ── Login ── */
.login-screen{position:fixed;inset:0;background:var(--dark);display:flex;align-items:center;justify-content:center;z-index:1000}
.login-box{background:var(--white);border-radius:20px;padding:2.5rem;width:380px;text-align:center}
.login-logo{font-size:1.1rem;font-weight:700;color:var(--gold);margin-bottom:.3rem}
.login-sub{font-size:.8rem;color:var(--light);margin-bottom:2rem}
.login-screen.hidden{display:none}
</style>
</head>
<body>
<!-- ══ LOGIN ══ -->
<div class="login-screen" id="loginScreen">
<div class="login-box">
<div class="login-logo">دکتر سوسن آل‌طه</div>
<div class="login-sub">پنل مدیریت محتوا</div>
<div class="form-group" style="margin-bottom:1rem">
<label>نام کاربری</label>
<input type="text" id="loginUser" value="admin" placeholder="نام کاربری"/>
</div>
<div class="form-group" style="margin-bottom:1.5rem">
<label>رمز عبور</label>
<input type="password" id="loginPass" value="admin123" placeholder="رمز عبور"/>
</div>
<button class="btn btn-primary" style="width:100%;justify-content:center" onclick="doLogin()">ورود به پنل</button>
<p id="loginErr" style="color:var(--danger);font-size:.8rem;margin-top:.8rem;display:none">نام کاربری یا رمز عبور اشتباه است</p>
</div>
</div>
<!-- ══ SIDEBAR ══ -->
<aside class="sidebar">
<div class="sidebar-logo">مدیریت سایت<span>دکتر سوسن آل‌طه</span></div>
<nav class="sidebar-nav">
<div class="nav-section">داشبورد</div>
<div class="nav-item active" onclick="showPage('dashboard',this)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/></svg>
داشبورد
</div>
<div class="nav-section">محتوای سایت</div>
<div class="nav-item" onclick="showPage('hero',this)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>
صفحه اصلی (هرو)
</div>
<div class="nav-item" onclick="showPage('about',this)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="8" r="4"/><path d="M4 20c0-4 3.6-7 8-7s8 3 8 7"/></svg>
درباره من
</div>
<div class="nav-item" onclick="showPage('contact',this)">
<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="nav-section">بخش‌های سایت</div>
<div class="nav-item" onclick="showPage('services',this)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/></svg>
خدمات
</div>
<div class="nav-item" onclick="showPage('gallery',this)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>
گالری
</div>
<div class="nav-item" onclick="showPage('testimonials',this)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
نظرات بیماران
</div>
<div class="nav-section">وبلاگ و SEO</div>
<div class="nav-item" onclick="showPage('blogposts',this)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
مقالات
</div>
<div class="nav-item" onclick="showPage('categories',this)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
دسته‌بندی‌ها
</div>
<div class="nav-item" onclick="showPage('faqs',this)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
سوالات متداول
</div>
<div class="nav-item" onclick="showPage('seo',this)">
<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('siteidentity',this)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/></svg>
هویت سایت (لوگو / فاویکون)
</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>
تغییر رمز عبور
</div>
<div class="nav-item" onclick="showPage('comments',this)" id="commentsNavItem">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>
نظرات <span id="pendingBadge" style="display:none;background:#E53935;color:#fff;font-size:.65rem;padding:.1rem .4rem;border-radius:50px;margin-right:.3rem"></span>
</div>
</nav>
<div class="sidebar-footer">API: <span id="apiLabel">localhost:5000</span></div>
</aside>
<!-- ══ MAIN ══ -->
<div class="main">
<div class="topbar">
<div class="topbar-title" id="pageTitle">داشبورد</div>
<div class="topbar-actions">
<a href="/" target="_blank" class="btn btn-secondary btn-sm">مشاهده سایت</a>
<button class="btn btn-danger btn-sm" onclick="logout()">خروج</button>
</div>
</div>
<div class="page-content">
<!-- ── DASHBOARD ── -->
<div class="page active" id="page-dashboard">
<div class="stat-grid" id="dashStats">
<div class="stat-card"><div class="icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg></div><div class="label">مقالات منتشرشده</div><div class="value" id="ds-posts">-</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="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 style="display:grid;grid-template-columns:1fr 1fr;gap:1.2rem;margin-bottom:1.2rem">
<div class="card">
<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>
<!-- ── HERO ── -->
<div class="page" id="page-hero">
<div class="card">
<div class="card-header"><div class="card-title">ویرایش بخش هرو</div></div>
<div class="card-body">
<div class="form-grid">
<div class="form-row">
<div class="form-group"><label>نام دکتر</label><input id="hero-name"/></div>
<div class="form-group"><label>تخصص (زیر نام)</label><input id="hero-title"/></div>
</div>
<div class="form-group"><label>توضیح کوتاه</label><textarea id="hero-subtitle" rows="2"></textarea></div>
<div class="form-row">
<div class="form-group"><label>آمار ۱ - عدد</label><input id="hero-stat1_num"/></div>
<div class="form-group"><label>آمار ۱ - برچسب</label><input id="hero-stat1_lbl"/></div>
</div>
<div class="form-row">
<div class="form-group"><label>آمار ۲ - عدد</label><input id="hero-stat2_num"/></div>
<div class="form-group"><label>آمار ۲ - برچسب</label><input id="hero-stat2_lbl"/></div>
</div>
<div class="form-row">
<div class="form-group"><label>آمار ۳ - عدد</label><input id="hero-stat3_num"/></div>
<div class="form-group"><label>آمار ۳ - برچسب</label><input id="hero-stat3_lbl"/></div>
</div>
<div class="form-row">
<div class="form-group"><label>دکمه اصلی</label><input id="hero-cta_primary"/></div>
<div class="form-group"><label>دکمه ثانویه</label><input id="hero-cta_secondary"/></div>
</div>
<div class="form-row">
<div class="form-group"><label>نشان شناور - عنوان</label><input id="hero-badge_title" placeholder="متخصص پوست و زیبایی"/></div>
<div class="form-group"><label>نشان شناور - زیرعنوان</label><input id="hero-badge_subtitle" placeholder="فارغ‌التحصیل دانشگاه ایران"/></div>
</div>
<div class="form-group"><label>نمایش نشان شناور</label><select id="hero-badge_hidden"><option value="false">نمایش</option><option value="true">مخفی</option></select></div>
<div class="form-group">
<label>📷 تصویر دکتر (هرو)</label>
<input type="hidden" id="hero-image"/>
<div class="input-upload-wrap">
<button class="upload-btn" id="upload-btn-hero-image" onclick="uploadImage('hero-image','prev-hero-image')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
آپلود تصویر
</button>
<button type="button" class="upload-remove" id="rm-hero-image" onclick="removeImage('hero-image','prev-hero-image')" style="display:none">حذف</button>
</div>
<img id="prev-hero-image" class="upload-preview" alt="تصویر هرو"/>
</div>
</div>
<div style="margin-top:1.5rem"><button class="btn btn-primary" onclick="saveSection('hero')">ذخیره تغییرات</button></div>
</div>
</div>
</div>
<!-- ── ABOUT ── -->
<div class="page" id="page-about">
<div class="card">
<div class="card-header"><div class="card-title">ویرایش بخش درباره من</div></div>
<div class="card-body">
<div class="form-grid">
<div class="form-row">
<div class="form-group"><label>عنوان</label><input id="about-title"/></div>
<div class="form-group"><label>سال‌های تجربه</label><input id="about-years_exp"/></div>
</div>
<div class="form-group"><label>بیوگرافی</label><textarea id="about-bio" rows="4"></textarea></div>
<div class="form-group"><label>دستاورد ۱</label><input id="about-cred1"/></div>
<div class="form-group"><label>دستاورد ۲</label><input id="about-cred2"/></div>
<div class="form-group"><label>دستاورد ۳</label><input id="about-cred3"/></div>
<div class="form-group"><label>دستاورد ۴</label><input id="about-cred4"/></div>
<div class="form-group"><label>دستاورد ۵</label><input id="about-cred5"/></div>
<div class="form-group">
<label>📷 تصویر بخش «درباره من»</label>
<input type="hidden" id="about-image"/>
<div class="input-upload-wrap">
<button class="upload-btn" id="upload-btn-about-image" onclick="uploadImage('about-image','prev-about-image')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
آپلود تصویر
</button>
<button type="button" class="upload-remove" id="rm-about-image" onclick="removeImage('about-image','prev-about-image')" style="display:none">حذف</button>
</div>
<img id="prev-about-image" class="upload-preview" alt="تصویر درباره من"/>
</div>
</div>
<div style="margin-top:1.5rem"><button class="btn btn-primary" onclick="saveSection('about')">ذخیره تغییرات</button></div>
</div>
</div>
</div>
<!-- ── CONTACT ── -->
<div class="page" id="page-contact">
<div class="card">
<div class="card-header"><div class="card-title">اطلاعات تماس</div></div>
<div class="card-body">
<div class="form-grid">
<div class="form-row">
<div class="form-group"><label>تلفن</label><input id="contact-phone"/></div>
<div class="form-group"><label>ایمیل</label><input id="contact-email"/></div>
</div>
<div class="form-group"><label>آدرس</label><input id="contact-address"/></div>
<div class="form-group"><label>ساعات کاری</label><input id="contact-hours"/></div>
<div class="form-row">
<div class="form-group"><label>لینک اینستاگرام</label><input id="contact-instagram" dir="ltr" placeholder="https://instagram.com/..."/></div>
<div class="form-group"><label>لینک واتساپ</label><input id="contact-whatsapp" dir="ltr" placeholder="https://wa.me/98..."/></div>
</div>
<div class="form-row">
<div class="form-group"><label>لینک تلگرام</label><input id="contact-telegram" dir="ltr" placeholder="https://t.me/..."/></div>
<div class="form-group"><label>لینک بله (Bale)</label><input id="contact-bale" dir="ltr" placeholder="https://ble.ir/..."/></div>
</div>
<div class="form-group"><label>لینک روبیکا (Rubika)</label><input id="contact-rubika" dir="ltr" placeholder="https://rubika.ir/..."/></div>
</div>
<div style="margin-top:1.5rem"><button class="btn btn-primary" onclick="saveSection('contact')">ذخیره تغییرات</button></div>
</div>
</div>
</div>
<!-- ── SERVICES ── -->
<div class="page" id="page-services">
<div class="card">
<div class="card-header">
<div class="card-title">خدمات</div>
<button class="btn btn-primary btn-sm" onclick="openSvcModal()">+ افزودن خدمت</button>
</div>
<div class="table-wrap">
<table><thead><tr><th>#</th><th>عنوان</th><th>توضیح</th><th>وضعیت</th><th>عملیات</th></tr></thead>
<tbody id="servicesTable"></tbody></table>
</div>
</div>
</div>
<!-- ── GALLERY ── -->
<div class="page" id="page-gallery">
<div class="card">
<div class="card-header">
<div class="card-title">گالری</div>
<button class="btn btn-primary btn-sm" onclick="openGalleryModal()">+ افزودن تصویر</button>
</div>
<div class="table-wrap">
<table><thead><tr><th>دسته</th><th>توضیح</th><th>وضعیت</th><th>عملیات</th></tr></thead>
<tbody id="galleryTable"></tbody></table>
</div>
</div>
</div>
<!-- ── TESTIMONIALS ── -->
<div class="page" id="page-testimonials">
<div class="card">
<div class="card-header">
<div class="card-title">نظرات بیماران</div>
<button class="btn btn-primary btn-sm" onclick="openTestimModal()">+ افزودن نظر</button>
</div>
<div class="table-wrap">
<table><thead><tr><th>نام</th><th>متن</th><th>امتیاز</th><th>وضعیت</th><th>عملیات</th></tr></thead>
<tbody id="testimonialsTable"></tbody></table>
</div>
</div>
</div>
<!-- ── BLOG POSTS ── -->
<div class="page" id="page-blogposts">
<div class="card">
<div class="card-header">
<div class="card-title">مقالات وبلاگ</div>
<button class="btn btn-primary btn-sm" onclick="openPostEditor()">+ مقاله جدید</button>
</div>
<div class="table-wrap">
<table><thead><tr><th>عنوان</th><th>دسته</th><th>کلیدواژه</th><th>بازدید</th><th>وضعیت</th><th>عملیات</th></tr></thead>
<tbody id="postsTable"></tbody></table>
</div>
</div>
</div>
<!-- ── CATEGORIES ── -->
<div class="page" id="page-categories">
<div class="card">
<div class="card-header">
<div class="card-title">دسته‌بندی‌ها</div>
<button class="btn btn-primary btn-sm" onclick="openCatModal()">+ دسته جدید</button>
</div>
<div class="table-wrap">
<table><thead><tr><th>نام</th><th>اسلاگ</th><th>مقالات</th><th>عملیات</th></tr></thead>
<tbody id="categoriesTable"></tbody></table>
</div>
</div>
</div>
<!-- ── SEO ── -->
<div class="page" id="page-seo">
<div class="stat-grid" style="grid-template-columns:repeat(3,1fr)">
<div class="stat-card"><div class="label">مقالات منتشرشده</div><div class="value" id="seo-posts">-</div></div>
<div class="stat-card"><div class="label">کل بازدید</div><div class="value" id="seo-views">-</div></div>
<div class="stat-card"><div class="label">مقالات بدون متا</div><div class="value" id="seo-nometa" style="color:var(--danger)">-</div></div>
</div>
<div class="card">
<div class="card-header"><div class="card-title">پربازدیدترین مقالات</div></div>
<div class="table-wrap"><table><thead><tr><th>عنوان</th><th>بازدید</th></tr></thead><tbody id="seoTopPosts"></tbody></table></div>
</div>
<div class="card">
<div class="card-header"><div class="card-title">راهنمای بهبود SEO</div></div>
<div class="card-body" style="font-size:.88rem;line-height:2;color:var(--mid)">
<p>✅ هر مقاله باید <strong>MetaTitle</strong> زیر ۷۰ کاراکتر داشته باشد</p>
<p><strong>MetaDescription</strong> بین ۱۵۰-۱۶۰ کاراکتر</p>
<p><strong>کلیدواژه اصلی</strong> در عنوان، اول پاراگراف و URL وجود داشته باشد</p>
<p>✅ هر مقاله حداقل <strong>۵۰۰ کلمه</strong> داشته باشد</p>
<p>✅ از <strong>H2 و H3</strong> در ساختار مقاله استفاده کنید</p>
<p>✅ لینک‌دهی داخلی بین مقالات انجام دهید</p>
<p>✅ تصاویر دارای <strong>alt text</strong> فارسی باشند</p>
<p>🔗 <a href="../sitemap.xml" target="_blank" style="color:var(--gold)">مشاهده Sitemap</a> | <a href="../robots.txt" target="_blank" style="color:var(--gold)">مشاهده Robots.txt</a></p>
</div>
</div>
</div>
<!-- ══ SECURITY PAGE ══ -->
<!-- ══ SITE IDENTITY ══ -->
<div class="page" id="page-siteidentity">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1.2rem">
<!-- Logo -->
<div class="card">
<div class="card-header"><div class="card-title">🖼️ لوگوی سایت</div></div>
<div class="modal-body">
<p style="font-size:.83rem;color:var(--mid);margin-bottom:1.2rem;line-height:1.8">
لوگو در هدر سایت و صفحات وبلاگ نمایش داده می‌شود.<br>
<strong>فرمت پیشنهادی:</strong> PNG با پس‌زمینه شفاف (transparent) — ابعاد: ۲۰۰×۶۰ پیکسل
</p>
<input type="hidden" id="si-logo"/>
<div class="input-upload-wrap" style="flex-wrap:wrap;gap:.5rem">
<button class="upload-btn" id="upload-btn-si-logo" onclick="uploadImage('si-logo','prev-si-logo')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
آپلود لوگو
</button>
<button type="button" class="upload-remove" id="rm-si-logo" onclick="removeImage('si-logo','prev-si-logo')" style="display:none">حذف</button>
</div>
<img id="prev-si-logo" class="upload-preview" alt="لوگو" style="max-height:80px;object-fit:contain;background:#f5f5f5;padding:.5rem"/>
<div style="margin-top:1.2rem">
<button class="btn btn-primary" onclick="saveSiteIdentity()">ذخیره لوگو</button>
</div>
</div>
</div>
<!-- Favicon -->
<div class="card">
<div class="card-header"><div class="card-title">⭐ فاویکون (Favicon)</div></div>
<div class="modal-body">
<p style="font-size:.83rem;color:var(--mid);margin-bottom:1.2rem;line-height:1.8">
آیکون کوچکی که در تب مرورگر نمایش داده می‌شود.<br>
<strong>فرمت پیشنهادی:</strong> PNG یا ICO — ابعاد: ۳۲×۳۲ یا ۶۴×۶۴ پیکسل
</p>
<input type="hidden" id="si-favicon"/>
<div class="input-upload-wrap" style="flex-wrap:wrap;gap:.5rem">
<button class="upload-btn" id="upload-btn-si-favicon" onclick="uploadImage('si-favicon','prev-si-favicon')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
آپلود فاویکون
</button>
<button type="button" class="upload-remove" id="rm-si-favicon" onclick="removeImage('si-favicon','prev-si-favicon')" style="display:none">حذف</button>
</div>
<img id="prev-si-favicon" class="upload-preview" alt="فاویکون" style="max-height:64px;max-width:64px;object-fit:contain;background:#f5f5f5;padding:.4rem;border-radius:8px"/>
<div style="margin-top:1.2rem">
<button class="btn btn-primary" onclick="saveSiteIdentity()">ذخیره فاویکون</button>
</div>
</div>
</div>
</div>
<!-- Preview -->
<div class="card" style="margin-top:1.2rem">
<div class="card-header"><div class="card-title">پیش‌نمایش</div></div>
<div class="modal-body">
<div style="display:flex;align-items:center;gap:2rem;flex-wrap:wrap">
<div>
<p style="font-size:.75rem;color:var(--light);margin-bottom:.5rem">هدر سایت</p>
<div style="background:#FAFAF7;border:1px solid var(--border);border-radius:10px;padding:.8rem 1.4rem;display:flex;align-items:center;gap:.8rem;min-width:220px">
<img id="preview-logo-header" src="" alt="" style="height:36px;object-fit:contain;display:none"/>
<span id="preview-logo-text" style="font-size:.95rem;font-weight:700;color:#B8955A">دکتر سوسن آل‌طه</span>
</div>
</div>
<div>
<p style="font-size:.75rem;color:var(--light);margin-bottom:.5rem">تب مرورگر</p>
<div style="background:#E8EAED;border-radius:8px 8px 0 0;padding:.4rem .9rem;display:inline-flex;align-items:center;gap:.5rem;font-size:.78rem;color:#333;min-width:180px">
<img id="preview-favicon-tab" src="" alt="" style="width:16px;height:16px;object-fit:contain;display:none"/>
<span id="preview-favicon-text">🌐</span>
<span style="color:#666;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">دکتر سوسن آل‌طه</span>
</div>
</div>
</div>
<p style="font-size:.78rem;color:var(--light);margin-top:1rem">⚠️ پس از ذخیره، تغییرات فاویکون ممکن است نیاز به پاک‌کردن کش مرورگر داشته باشد.</p>
</div>
</div>
</div>
<div class="page" id="page-security">
<div class="card" style="max-width:480px">
<div class="card-header"><div class="card-title">🔒 تغییر رمز عبور مدیر</div></div>
<div class="card-body">
<div class="form-grid">
<div class="form-group">
<label>رمز عبور فعلی</label>
<input type="password" id="pw-current" placeholder="رمز عبور فعلی را وارد کنید"/>
</div>
<div class="form-group">
<label>رمز عبور جدید</label>
<input type="password" id="pw-new" placeholder="حداقل ۶ کاراکتر"/>
</div>
<div class="form-group">
<label>تکرار رمز عبور جدید</label>
<input type="password" id="pw-confirm" placeholder="رمز عبور جدید را تکرار کنید"/>
</div>
</div>
<div id="pw-msg" style="display:none;margin-top:.8rem;padding:.7rem 1rem;border-radius:8px;font-size:.85rem"></div>
<div style="margin-top:1.2rem;display:flex;gap:.7rem">
<button class="btn btn-primary" onclick="changePassword()">ذخیره رمز جدید</button>
<button class="btn btn-secondary" onclick="document.getElementById('pw-current').value=document.getElementById('pw-new').value=document.getElementById('pw-confirm').value=''">پاک کردن</button>
</div>
<div style="margin-top:1.5rem;padding:1rem;background:var(--bg);border-radius:8px;font-size:.82rem;color:var(--light);line-height:1.8">
<strong style="color:var(--mid)">نکات امنیتی:</strong><br/>
• از ترکیب حروف، اعداد و نماد استفاده کنید<br/>
• رمز عبور را در جای امنی نگه دارید<br/>
• پس از تغییر رمز، مجدداً وارد شوید
</div>
</div>
</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">
<div class="card-header">
<div class="card-title">مدیریت نظرات</div>
<button class="btn btn-secondary btn-sm" onclick="loadComments()">🔄 بازخوانی</button>
</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="commentsTable"></tbody>
</table>
</div>
</div>
</div>
<!-- ── FAQS ── -->
<div class="page" id="page-faqs">
<div class="card">
<div class="card-header">
<div class="card-title">سوالات متداول</div>
<button class="btn btn-primary btn-sm" onclick="openFaqModal()">+ افزودن سوال</button>
</div>
<div class="table-wrap">
<table><thead><tr><th>#</th><th>سوال</th><th>وضعیت</th><th>عملیات</th></tr></thead>
<tbody id="faqsTable"></tbody></table>
</div>
</div>
</div>
</div><!-- /page-content -->
</div><!-- /main -->
<!-- ══ MODALS ══ -->
<!-- Service Modal -->
<div class="modal-overlay hidden" id="svcModal">
<div class="modal">
<div class="modal-header"><div class="modal-title" id="svcModalTitle">افزودن خدمت</div><button class="modal-close" onclick="closeModal('svcModal')"></button></div>
<div class="modal-body">
<input type="hidden" id="svc-id"/>
<input type="hidden" id="svc-icon"/>
<div class="form-grid">
<div class="form-group"><label>عنوان</label><input id="svc-title"/></div>
<div class="form-group"><label>توضیح</label><textarea id="svc-desc" rows="3"></textarea></div>
<div class="form-row">
<div class="form-group"><label>ترتیب</label><input type="number" id="svc-order" value="1"/></div>
<div class="form-group"><label>وضعیت</label><select id="svc-active"><option value="true">فعال</option><option value="false">غیرفعال</option></select></div>
</div>
<div class="icon-picker-section">
<label>آیکون خدمت</label>
<div class="icon-selected-preview" id="svc-icon-preview" onclick="toggleIconGrid()">
<div class="icon-preview-box" id="svc-icon-box"></div>
<span class="icon-preview-label" id="svc-icon-label">انتخاب آیکون ▾</span>
</div>
<div class="icon-grid-wrap" id="svc-icon-grid-wrap">
<div class="icon-grid" id="svc-icon-grid"></div>
</div>
</div>
<div class="ba-pair">
<div class="form-group">
<label>📷 تصویر قبل از درمان</label>
<input type="hidden" id="svc-before"/>
<div class="input-upload-wrap">
<button class="upload-btn" id="upload-btn-svc-before" onclick="uploadImage('svc-before','prev-svc-before')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
آپلود تصویر
</button>
<button type="button" class="upload-remove" id="rm-svc-before" onclick="removeImage('svc-before','prev-svc-before')" style="display:none">حذف</button>
</div>
<img id="prev-svc-before" class="upload-preview" alt="قبل"/>
</div>
<div class="form-group">
<label>✨ تصویر بعد از درمان</label>
<input type="hidden" id="svc-after"/>
<div class="input-upload-wrap">
<button class="upload-btn" id="upload-btn-svc-after" onclick="uploadImage('svc-after','prev-svc-after')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
آپلود تصویر
</button>
<button type="button" class="upload-remove" id="rm-svc-after" onclick="removeImage('svc-after','prev-svc-after')" style="display:none">حذف</button>
</div>
<img id="prev-svc-after" class="upload-preview" alt="بعد"/>
</div>
</div>
</div>
</div>
<div class="modal-footer"><button class="btn btn-primary" onclick="saveSvc()">ذخیره</button><button class="btn btn-secondary" onclick="closeModal('svcModal')">انصراف</button></div>
</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 -->
<!-- Health Request Detail Modal -->
<div class="modal-overlay hidden" id="reqDetailModal">
<div class="modal" style="max-width:560px">
<div class="modal-header">
<div class="modal-title">جزئیات درخواست</div>
<button class="modal-close" onclick="closeModal('reqDetailModal')"></button>
</div>
<div class="modal-body" id="reqDetailBody"></div>
</div>
</div>
<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">
<div class="modal-header"><div class="modal-title" id="galleryModalTitle">افزودن تصویر</div><button class="modal-close" onclick="closeModal('galleryModal')"></button></div>
<div class="modal-body">
<input type="hidden" id="gal-id"/>
<div class="form-grid">
<div class="form-group">
<label>تصویر اصلی</label>
<input type="hidden" id="gal-img"/>
<div class="input-upload-wrap">
<button class="upload-btn" id="upload-btn-gal-img" onclick="uploadImage('gal-img','prev-gal-img')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
آپلود تصویر
</button>
<button type="button" class="upload-remove" id="rm-gal-img" onclick="removeImage('gal-img','prev-gal-img')" style="display:none">حذف</button>
</div>
<img id="prev-gal-img" class="upload-preview" alt="پیش‌نمایش"/>
</div>
<div class="form-row">
<div class="form-group">
<label>تصویر قبل</label>
<input type="hidden" id="gal-before"/>
<div class="input-upload-wrap">
<button class="upload-btn" id="upload-btn-gal-before" onclick="uploadImage('gal-before','prev-gal-before')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
آپلود تصویر
</button>
<button type="button" class="upload-remove" id="rm-gal-before" onclick="removeImage('gal-before','prev-gal-before')" style="display:none">حذف</button>
</div>
<img id="prev-gal-before" class="upload-preview" alt="پیش‌نمایش قبل"/>
</div>
<div class="form-group">
<label>تصویر بعد</label>
<input type="hidden" id="gal-after"/>
<div class="input-upload-wrap">
<button class="upload-btn" id="upload-btn-gal-after" onclick="uploadImage('gal-after','prev-gal-after')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
آپلود تصویر
</button>
<button type="button" class="upload-remove" id="rm-gal-after" onclick="removeImage('gal-after','prev-gal-after')" style="display:none">حذف</button>
</div>
<img id="prev-gal-after" class="upload-preview" alt="پیش‌نمایش بعد"/>
</div>
</div>
<div class="form-row">
<div class="form-group"><label>دسته‌بندی</label><input id="gal-cat" placeholder="بوتاکس، لیزر..."/></div>
<div class="form-group"><label>ترتیب</label><input type="number" id="gal-order" value="1"/></div>
</div>
<div class="form-group"><label>توضیح</label><input id="gal-caption"/></div>
<div class="form-group"><label>وضعیت</label><select id="gal-active"><option value="true">فعال</option><option value="false">غیرفعال</option></select></div>
</div>
</div>
<div class="modal-footer"><button class="btn btn-primary" onclick="saveGallery()">ذخیره</button><button class="btn btn-secondary" onclick="closeModal('galleryModal')">انصراف</button></div>
</div>
</div>
<!-- Testimonial Modal -->
<div class="modal-overlay hidden" id="testimModal">
<div class="modal">
<div class="modal-header"><div class="modal-title">افزودن / ویرایش نظر</div><button class="modal-close" onclick="closeModal('testimModal')"></button></div>
<div class="modal-body">
<input type="hidden" id="testim-id"/>
<div class="form-grid">
<div class="form-row">
<div class="form-group"><label>نام</label><input id="testim-name"/></div>
<div class="form-group"><label>ایموجی</label><input id="testim-emoji" value="👩"/></div>
</div>
<div class="form-group"><label>متن نظر</label><textarea id="testim-text" rows="3"></textarea></div>
<div class="form-row">
<div class="form-group"><label>امتیاز (۱-۵)</label><input type="number" id="testim-rating" value="5" min="1" max="5"/></div>
<div class="form-group"><label>تاریخ</label><input id="testim-date" placeholder="اردیبهشت ۱۴۰۳"/></div>
</div>
<div class="form-group"><label>وضعیت</label><select id="testim-active"><option value="true">فعال</option><option value="false">غیرفعال</option></select></div>
</div>
</div>
<div class="modal-footer"><button class="btn btn-primary" onclick="saveTestim()">ذخیره</button><button class="btn btn-secondary" onclick="closeModal('testimModal')">انصراف</button></div>
</div>
</div>
<!-- Category Modal -->
<div class="modal-overlay hidden" id="catModal">
<div class="modal" style="max-width:480px">
<div class="modal-header"><div class="modal-title" id="catModalTitle">دسته جدید</div><button class="modal-close" onclick="closeModal('catModal')"></button></div>
<div class="modal-body">
<input type="hidden" id="cat-id"/>
<div class="form-grid">
<div class="form-group"><label>نام</label><input id="cat-name"/></div>
<div class="form-group"><label>اسلاگ (خودکار)</label><input id="cat-slug" dir="ltr" placeholder="auto-generated"/></div>
<div class="form-group"><label>توضیح</label><textarea id="cat-desc" rows="2"></textarea></div>
</div>
</div>
<div class="modal-footer"><button class="btn btn-primary" onclick="saveCat()">ذخیره</button><button class="btn btn-secondary" onclick="closeModal('catModal')">انصراف</button></div>
</div>
</div>
<!-- Blog Post Editor Modal -->
<div class="modal-overlay hidden" id="postModal">
<div class="modal" style="max-width:900px">
<div class="modal-header"><div class="modal-title" id="postModalTitle">مقاله جدید</div><button class="modal-close" onclick="closeModal('postModal')"></button></div>
<div class="modal-body">
<input type="hidden" id="post-id"/>
<div class="form-grid">
<div class="form-group"><label>عنوان مقاله</label><input id="post-title" oninput="autoSlug()"/></div>
<div class="form-row">
<div class="form-group"><label>اسلاگ (URL)</label><input id="post-slug" dir="ltr"/></div>
<div class="form-group"><label>دسته‌بندی</label><select id="post-category"><option value="">انتخاب دسته...</option></select></div>
</div>
<div class="form-group"><label>خلاصه (Excerpt)</label><textarea id="post-excerpt" rows="2"></textarea></div>
<div class="form-group">
<label>محتوای مقاله</label>
<div class="editor-toolbar">
<button onclick="fmt('bold')"><b>B</b></button>
<button onclick="fmt('italic')"><i>I</i></button>
<button onclick="fmtBlock('h2')">H2</button>
<button onclick="fmtBlock('h3')">H3</button>
<button onclick="fmtBlock('p')">P</button>
<button onclick="fmt('insertUnorderedList')">• لیست</button>
<button onclick="fmt('insertOrderedList')">۱. لیست</button>
<button onclick="insLink()">🔗 لینک</button>
</div>
<div class="editor-content" id="post-content" contenteditable="true" dir="rtl"></div>
</div>
<div style="border-top:1px solid var(--border);padding-top:1rem;margin-top:.5rem">
<p style="font-size:.82rem;font-weight:600;color:var(--gold);margin-bottom:1rem">تنظیمات SEO</p>
<div class="form-group"><label>کلیدواژه اصلی (Focus Keyword)</label><input id="post-focus" oninput="checkSeo()"/><div class="form-hint">کلیدواژه‌ای که می‌خواهید برای آن رتبه بگیرید</div></div>
<div id="seoFeedback" style="margin:.5rem 0"></div>
<div class="form-group"><label>Meta Title <span style="font-weight:400;color:var(--light)" id="mtLen">(0/70)</span></label><input id="post-metatitle" oninput="updateLen('post-metatitle','mtLen',70)"/></div>
<div class="form-group"><label>Meta Description <span style="font-weight:400;color:var(--light)" id="mdLen">(0/160)</span></label><textarea id="post-metadesc" rows="2" oninput="updateLen('post-metadesc','mdLen',160)"></textarea></div>
<div class="form-group"><label>کلیدواژه‌ها (با کاما جدا کنید)</label><input id="post-keywords" placeholder="بوتاکس, جوانسازی, پوست..."/></div>
<div class="form-group">
<label>📷 تصویر شاخص</label>
<input type="hidden" id="post-image"/>
<div class="input-upload-wrap">
<button class="upload-btn" id="upload-btn-post-image" onclick="uploadImage('post-image','prev-post-image')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
آپلود تصویر
</button>
<button type="button" class="upload-remove" id="rm-post-image" onclick="removeImage('post-image','prev-post-image')" style="display:none">حذف</button>
</div>
<img id="prev-post-image" class="upload-preview" alt="تصویر شاخص"/>
</div>
<div class="form-row">
<div class="form-group"><label>نوع Schema</label><select id="post-schematype"><option value="MedicalWebPage">MedicalWebPage</option><option value="Article">Article</option><option value="FAQPage">FAQPage</option></select></div>
<div class="form-group"><label>وضعیت انتشار</label><select id="post-published"><option value="false">پیش‌نویس</option><option value="true">منتشر</option></select></div>
</div>
</div>
</div>
</div>
<div class="modal-footer"><button class="btn btn-primary" onclick="savePost()">ذخیره مقاله</button><button class="btn btn-secondary" onclick="closeModal('postModal')">انصراف</button></div>
</div>
</div>
<!-- FAQ Modal -->
<div class="modal-overlay hidden" id="faqModal">
<div class="modal" style="max-width:640px">
<div class="modal-header"><div class="modal-title" id="faqModalTitle">افزودن سوال</div><button class="modal-close" onclick="closeModal('faqModal')"></button></div>
<div class="modal-body">
<input type="hidden" id="faq-id"/>
<div class="form-grid">
<div class="form-group"><label>سوال</label><input id="faq-question"/></div>
<div class="form-group"><label>پاسخ</label><textarea id="faq-answer" rows="4"></textarea></div>
<div class="form-row">
<div class="form-group"><label>ترتیب</label><input type="number" id="faq-order" value="1"/></div>
<div class="form-group"><label>وضعیت</label><select id="faq-active"><option value="true">فعال</option><option value="false">غیرفعال</option></select></div>
</div>
</div>
</div>
<div class="modal-footer"><button class="btn btn-primary" onclick="saveFaq()">ذخیره</button><button class="btn btn-secondary" onclick="closeModal('faqModal')">انصراف</button></div>
</div>
</div>
<!-- Toast container -->
<div class="toast-container" id="toastContainer"></div>
<script>
const API = '';
let token = localStorage.getItem('dr_token') || '';
// ── Auth ──────────────────────────────────────────────────────────────────────
async function doLogin() {
const u = document.getElementById('loginUser').value;
const p = document.getElementById('loginPass').value;
const r = await fetch(`${API}/api/auth/login`, {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({username:u, password:p})
});
if (!r.ok) { document.getElementById('loginErr').style.display='block'; return; }
const d = await r.json();
token = d.token;
localStorage.setItem('dr_token', token);
document.getElementById('loginScreen').classList.add('hidden');
init();
}
function logout() {
localStorage.removeItem('dr_token');
location.reload();
}
async function changePassword() {
const cur = document.getElementById('pw-current').value;
const nw = document.getElementById('pw-new').value;
const cfm = document.getElementById('pw-confirm').value;
const msg = document.getElementById('pw-msg');
const show = (text, ok) => {
msg.textContent = text;
msg.style.cssText = `display:block;padding:.7rem 1rem;border-radius:8px;font-size:.85rem;background:${ok?'#E8F5E9':'#FFEBEE'};color:${ok?'#2E7D32':'#C62828'}`;
};
if (!cur || !nw || !cfm) { show('همه فیلدها الزامی هستند.', false); return; }
if (nw !== cfm) { show('رمز عبور جدید و تکرار آن یکسان نیستند.', false); return; }
if (nw.length < 6) { show('رمز عبور جدید باید حداقل ۶ کاراکتر باشد.', false); return; }
const result = await api('/api/auth/change-password', {
method: 'POST',
body: JSON.stringify({ currentPassword: cur, newPassword: nw })
});
if (result) {
show(result.message || 'رمز عبور با موفقیت تغییر کرد. لطفاً مجدداً وارد شوید.', true);
document.getElementById('pw-current').value = '';
document.getElementById('pw-new').value = '';
document.getElementById('pw-confirm').value = '';
setTimeout(() => { logout(); }, 2500);
}
}
// ── API helper ────────────────────────────────────────────────────────────────
async function api(path, opts={}) {
const h = {'Content-Type':'application/json'};
if (token) h['Authorization'] = `Bearer ${token}`;
const r = await fetch(`${API}${path}`, {...opts, headers:{...h, ...(opts.headers||{})}});
if (r.status === 401) { logout(); return null; }
if (r.status === 204) return null;
if (!r.ok) { toast('خطا در سرور: ' + r.status, 'error'); return null; }
return r.json().catch(()=>null);
}
function toast(msg, type='success') {
const c = document.getElementById('toastContainer');
const el = document.createElement('div');
el.className = `toast ${type}`;
el.textContent = msg;
c.appendChild(el);
setTimeout(()=>el.remove(), 3500);
}
// ── Navigation ────────────────────────────────────────────────────────────────
const pageTitles = {dashboard:'داشبورد',hero:'صفحه اصلی',about:'درباره من',contact:'تماس',services:'خدمات',gallery:'گالری',testimonials:'نظرات',blogposts:'مقالات',categories:'دسته‌بندی‌ها',faqs:'سوالات متداول',seo:'گزارش SEO',comments:'مدیریت نظرات',security:'تغییر رمز عبور',patients:'پرونده بیماران','patient-profile':'پرونده بیمار',healthrequests:'درخواست‌های سلامت',siteidentity:'هویت سایت (لوگو / فاویکون)'};
function showPage(name, el) {
document.querySelectorAll('.page').forEach(p=>p.classList.remove('active'));
document.querySelectorAll('.nav-item').forEach(n=>n.classList.remove('active'));
document.getElementById('page-'+name).classList.add('active');
el.classList.add('active');
document.getElementById('pageTitle').textContent = pageTitles[name] || name;
loadPage(name);
}
function loadPage(name) {
if (name==='dashboard') loadDashboard();
else if (name==='hero') loadSection('hero');
else if (name==='about') loadSection('about');
else if (name==='contact') loadSection('contact');
else if (name==='services') loadServices();
else if (name==='gallery') loadGallery();
else if (name==='testimonials') loadTestimonials();
else if (name==='blogposts') loadPosts();
else if (name==='categories') loadCategories();
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();
else if (name==='siteidentity') loadSiteIdentity();
}
// ── 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 ───────────────────────────────────────────────────────────
let _allReqs = [];
async function loadHealthRequests() {
const filter = document.getElementById('reqFilter').value;
const params = filter !== '' ? `?handled=${filter}` : '';
_allReqs = await api('/api/health-requests' + params) || [];
const pending = _allReqs.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 = _allReqs.map(r=>`
<tr style="${!r.isHandled?'font-weight:600':'opacity:.75'}">
<td>${r.fullName}</td>
<td dir="ltr">${r.phoneNumber}</td>
<td>${catLabel[r.category]||r.category}</td>
<td style="max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--mid);font-size:.85rem">${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 style="white-space:nowrap">
<button class="btn btn-secondary btn-sm" onclick="viewReq(${r.id})">مشاهده</button>
${!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>';
}
function viewReq(id) {
const r = _allReqs.find(x=>x.id===id); if(!r) return;
const catLabel = {beauty:'زیبایی پوست', health:'سلامت عمومی'};
document.getElementById('reqDetailBody').innerHTML = `
<div style="display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-bottom:1.2rem">
<div><span style="font-size:.75rem;color:var(--light);display:block;margin-bottom:.25rem">نام</span><strong>${r.fullName}</strong></div>
<div><span style="font-size:.75rem;color:var(--light);display:block;margin-bottom:.25rem">تلفن</span><strong dir="ltr">${r.phoneNumber}</strong></div>
<div><span style="font-size:.75rem;color:var(--light);display:block;margin-bottom:.25rem">ایمیل</span><span>${r.email||'—'}</span></div>
<div><span style="font-size:.75rem;color:var(--light);display:block;margin-bottom:.25rem">دسته</span>
<span style="background:${r.category==='health'?'#E3F2FD':'var(--gold-pale)'};color:${r.category==='health'?'#1565C0':'var(--gold)'};padding:3px 10px;border-radius:20px;font-size:.78rem">${catLabel[r.category]||r.category}</span>
</div>
<div><span style="font-size:.75rem;color:var(--light);display:block;margin-bottom:.25rem">تاریخ ثبت</span><span>${new Date(r.createdAt).toLocaleDateString('fa-IR')}</span></div>
<div><span style="font-size:.75rem;color:var(--light);display:block;margin-bottom:.25rem">وضعیت</span>
<span style="background:${r.isHandled?'#E8F5E9':'#FFEBEE'};color:${r.isHandled?'#388E3C':'#C62828'};padding:3px 10px;border-radius:20px;font-size:.78rem">${r.isHandled?'بررسی شده':'جدید'}</span>
</div>
</div>
<div><span style="font-size:.75rem;color:var(--light);display:block;margin-bottom:.5rem">پیام / شرح درخواست</span>
<div style="background:var(--section-bg);border-radius:12px;padding:1rem 1.2rem;line-height:2;font-size:.9rem;white-space:pre-wrap;min-height:60px">${r.message||'—'}</div>
</div>
${!r.isHandled?`<div style="margin-top:1.2rem"><button class="btn btn-primary" onclick="handleReq(${r.id});closeModal('reqDetailModal')">✓ علامت‌گذاری به عنوان بررسی شده</button></div>`:''}
`;
document.getElementById('reqDetailModal').classList.remove('hidden');
}
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 ──────────────────────────────────────────────────────────────────
// ── Site Identity (logo / favicon) ────────────────────────────────────────────
async function loadSiteIdentity() {
const data = await api('/api/settings/identity') || [];
const vals = {};
data.forEach(s => vals[s.key] = s.value);
// Set hidden inputs
document.getElementById('si-logo').value = vals.logo || '';
document.getElementById('si-favicon').value = vals.favicon || '';
// Show previews
showPreview('prev-si-logo', vals.logo || '');
showPreview('prev-si-favicon', vals.favicon || '');
// Update live preview panel
updateSiteIdentityPreview(vals.logo || '', vals.favicon || '');
}
function updateSiteIdentityPreview(logoUrl, faviconUrl) {
const logoImg = document.getElementById('preview-logo-header');
const logoTxt = document.getElementById('preview-logo-text');
if (logoUrl) { logoImg.src=logoUrl; logoImg.style.display='block'; logoTxt.style.display='none'; }
else { logoImg.style.display='none'; logoTxt.style.display=''; }
const favImg = document.getElementById('preview-favicon-tab');
const favTxt = document.getElementById('preview-favicon-text');
if (faviconUrl) { favImg.src=faviconUrl; favImg.style.display='inline'; favTxt.style.display='none'; }
else { favImg.style.display='none'; favTxt.style.display=''; }
}
async function saveSiteIdentity() {
const logo = document.getElementById('si-logo').value;
const favicon = document.getElementById('si-favicon').value;
await api('/api/settings/identity', {
method: 'PUT',
body: JSON.stringify({ settings: { logo, favicon } })
});
updateSiteIdentityPreview(logo, favicon);
toast('ذخیره شد ✓ — برای اعمال فاویکون کش مرورگر را پاک کنید');
}
// Live preview while uploading
document.addEventListener('change', () => {
const logo = document.getElementById('si-logo')?.value || '';
const favicon = document.getElementById('si-favicon')?.value || '';
if (logo || favicon) updateSiteIdentityPreview(logo, favicon);
});
async function loadComments() {
const all = await api('/api/comments') || [];
const pending = all.filter(c => !c.isApproved);
// update badge
const badge = document.getElementById('pendingBadge');
if (pending.length > 0) { badge.textContent = pending.length; badge.style.display = 'inline'; }
else badge.style.display = 'none';
// update dashboard stat
const dsEl = document.getElementById('ds-testimonials');
if (dsEl) dsEl.textContent = pending.length + ' نظر در انتظار';
const tbody = document.getElementById('commentsTable');
if (!tbody) return;
if (!all.length) { tbody.innerHTML = '<tr><td colspan="7" style="text-align:center;color:var(--light);padding:2rem">هیچ نظری ثبت نشده</td></tr>'; return; }
tbody.innerHTML = all.filter(c=>!c.isAdminReply).map(c => `
<tr id="ctr-${c.id}">
<td><strong>${esc(c.authorName)}</strong></td>
<td style="font-size:.78rem;color:var(--light)">${esc(c.authorEmail||'—')}</td>
<td style="max-width:220px;white-space:normal;font-size:.83rem">${esc(c.body)}</td>
<td><a href="/blog/${c.postSlug}" target="_blank" style="color:var(--gold);font-size:.8rem">${esc(c.postTitle||'—')}</a></td>
<td style="font-size:.78rem;color:var(--light)">${new Date(c.createdAt).toLocaleDateString('fa-IR')}</td>
<td><span class="badge ${c.isApproved?'badge-green':'badge-red'}">${c.isApproved?'تأییدشده':'در انتظار'}</span></td>
<td style="white-space:nowrap">
${!c.isApproved?`<button class="btn btn-secondary btn-sm" onclick="approveComment(${c.id})">✓ تأیید</button> `:''}
<button class="btn btn-secondary btn-sm" onclick="toggleReplyBox(${c.id})" style="color:var(--gold)">↩ پاسخ</button>
<button class="btn btn-danger btn-sm" onclick="deleteComment(${c.id})">حذف</button>
</td>
</tr>
<tr id="reply-row-${c.id}" style="display:none;background:var(--gold-pale)">
<td colspan="7" style="padding:.8rem 1.2rem">
<div style="display:flex;gap:.6rem;align-items:flex-start">
<div style="font-size:.78rem;color:var(--gold);font-weight:600;padding-top:.5rem;white-space:nowrap">↩ پاسخ به ${esc(c.authorName)}:</div>
<textarea id="replyText-${c.id}" rows="2" style="flex:1;border:1.5px solid var(--border);border-radius:8px;padding:.5rem .8rem;font-family:inherit;font-size:.85rem;direction:rtl;resize:none" placeholder="پاسخ خود را بنویسید..."></textarea>
<button class="btn btn-primary btn-sm" onclick="sendReply(${c.id})">ارسال</button>
<button class="btn btn-secondary btn-sm" onclick="toggleReplyBox(${c.id})">✕</button>
</div>
</td>
</tr>`).join('');
}
function esc(s){return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
async function approveComment(id) {
await api(`/api/comments/${id}/approve`, {method:'PUT'});
toast('نظر تأیید شد ✓');
loadComments();
}
async function deleteComment(id) {
if (!confirm('این نظر حذف شود؟')) return;
await api(`/api/comments/${id}`, {method:'DELETE'});
toast('نظر حذف شد', 'error');
loadComments();
}
function toggleReplyBox(id) {
const row = document.getElementById(`reply-row-${id}`);
if (!row) return;
const visible = row.style.display !== 'none';
row.style.display = visible ? 'none' : 'table-row';
if (!visible) document.getElementById(`replyText-${id}`)?.focus();
}
async function sendReply(id) {
const textarea = document.getElementById(`replyText-${id}`);
const body = textarea?.value?.trim();
if (!body) { toast('متن پاسخ را وارد کنید', 'error'); return; }
const result = await api(`/api/comments/${id}/reply`, {
method: 'POST',
body: JSON.stringify({ body })
});
if (result) { toast('پاسخ ارسال شد ✓'); loadComments(); }
}
// ── Dashboard ─────────────────────────────────────────────────────────────────
async function loadDashboard() {
const [seo, testims, patients, reqs] = await Promise.all([
api('/api/seo/stats'),
api('/api/testimonials/all'),
api('/api/patients'),
api('/api/health-requests')
]);
if (seo) {
document.getElementById('ds-posts').textContent = seo.total;
document.getElementById('ds-views').textContent = seo.views;
document.getElementById('ds-nometa').textContent = seo.noMeta;
const tb = document.getElementById('topPostsTable');
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 (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 ──────────────────────────────────────────────────────────
async function loadSection(sec) {
const data = await api(`/api/settings/${sec}`);
if (!data) return;
Object.entries(data).forEach(([k,v])=>{
const el = document.getElementById(`${sec}-${k}`);
if (el) {
el.value = v;
// auto-show image preview if a corresponding prev- element exists
const prev = document.getElementById(`prev-${sec}-${k}`);
if (prev) syncPreview(`${sec}-${k}`, `prev-${sec}-${k}`);
}
});
}
async function saveSection(sec) {
const inputs = document.querySelectorAll(`[id^="${sec}-"]`);
const settings = {};
inputs.forEach(el=>{
const key = el.id.replace(`${sec}-`,'');
settings[key] = el.value;
});
await api(`/api/settings/${sec}`, {method:'PUT', body:JSON.stringify({settings})});
toast('تغییرات با موفقیت ذخیره شد ✓');
}
// ── Service Icon Picker ───────────────────────────────────────────────────────
const SERVICE_ICONS = [
{key:'syringe', label:'تزریق', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 2l4 4-4 4"/><path d="M22 6H9"/><path d="M7 8l-5 5 5 5"/><path d="M2 13h10"/><path d="M9 6v12"/><circle cx="14" cy="18" r="2"/></svg>'},
{key:'laser', label:'لیزر', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/><circle cx="12" cy="12" r="3"/></svg>'},
{key:'face', label:'صورت', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="8" r="5"/><path d="M3 21c0-4.418 4.03-8 9-8s9 3.582 9 8"/></svg>'},
{key:'eye', label:'چشم', svg:'<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>'},
{key:'smile', label:'رضایت', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/></svg>'},
{key:'sparkle', label:'جوانسازی', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3l1.5 4.5L18 9l-4.5 1.5L12 15l-1.5-4.5L6 9l4.5-1.5L12 3z"/><path d="M19 2l.75 2.25L22 5l-2.25.75L19 8l-.75-2.25L16 5l2.25-.75L19 2z"/></svg>'},
{key:'drop', label:'هیدراتاسیون',svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2.69l5.66 5.66a8 8 0 1 1-11.31 0z"/></svg>'},
{key:'leaf', label:'طبیعی', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 8C8 10 5.9 16.17 3.82 19.5c1.17.17 2.35.5 3.18.5C12 20 17 14 17 8z"/><path d="M3 22l2-4"/></svg>'},
{key:'sun', label:'نور درمانی',svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>'},
{key:'zap', label:'انرژی', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>'},
{key:'shield', label:'محافظت', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>'},
{key:'heart', label:'زیبایی', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>'},
{key:'star', label:'برتر', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>'},
{key:'award', label:'کیفیت', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="8" r="6"/><path d="M15.477 12.89L17 22l-5-3-5 3 1.523-9.11"/></svg>'},
{key:'gem', label:'لوکس', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="6 3 18 3 22 9 12 22 2 9"/><polyline points="22 9 12 9 6 3"/><line x1="12" y1="22" x2="12" y2="9"/><line x1="2" y1="9" x2="6" y2="3"/><line x1="22" y1="9" x2="18" y2="3"/></svg>'},
{key:'scissors', label:'اصلاح', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="6" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><line x1="20" y1="4" x2="8.12" y2="15.88"/><line x1="14.47" y1="14.48" x2="20" y2="20"/><line x1="8.12" y1="8.12" x2="12" y2="12"/></svg>'},
{key:'feather', label:'لطیف', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.24 12.24a6 6 0 0 0-8.49-8.49L5 10.5V19h8.5z"/><line x1="16" y1="8" x2="2" y2="22"/><line x1="17.5" y1="15" x2="9" y2="15"/></svg>'},
{key:'activity', label:'نشاط', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/></svg>'},
{key:'refresh', label:'ترمیم', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>'},
{key:'plus', label:'پزشکی', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="16"/><line x1="8" y1="12" x2="16" y2="12"/></svg>'},
{key:'wind', label:'مراقبت', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9.59 4.59A2 2 0 1 1 11 8H2m10.59 11.41A2 2 0 1 0 14 16H2m15.73-8.27A2 2 0 1 1 19.5 12H2"/></svg>'},
{key:'wand', label:'جادوی زیبایی',svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 4V2m0 14v-2M8 9h2m10 0h2m-4.2 2.8L17.2 13.2M17.2 4.8l1.4 1.4M10.8 11.8L9.4 13.2M10.8 4.8L9.4 3.4"/><path d="M3 21l9-9"/></svg>'},
{key:'user', label:'کلینیک', svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>'},
{key:'package', label:'بسته درمانی',svg:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="16.5" y1="9.4" x2="7.5" y2="4.21"/><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>'},
];
function renderIconPicker(selectedSvg) {
const grid = document.getElementById('svc-icon-grid');
grid.innerHTML = SERVICE_ICONS.map(ic => {
const isSel = selectedSvg && selectedSvg.trim() === ic.svg.trim();
return `<div class="icon-opt${isSel?' selected':''}" title="${ic.label}" onclick="selectSvcIcon(this,'${ic.key}')">${ic.svg}<span>${ic.label}</span></div>`;
}).join('');
// Set preview to the matched icon, or first as default
const toSelect = selectedSvg ? (SERVICE_ICONS.find(ic=>ic.svg.trim()===selectedSvg.trim()) || SERVICE_ICONS[0]) : SERVICE_ICONS[0];
document.getElementById('svc-icon').value = toSelect.svg;
document.getElementById('svc-icon-box').innerHTML = toSelect.svg;
document.getElementById('svc-icon-label').textContent = toSelect.label + ' ▾';
if (!selectedSvg) {
const firstEl = grid.querySelector('.icon-opt');
if (firstEl) firstEl.classList.add('selected');
}
}
function selectSvcIcon(el, key) {
document.querySelectorAll('#svc-icon-grid .icon-opt').forEach(e=>e.classList.remove('selected'));
el.classList.add('selected');
const ic = SERVICE_ICONS.find(x=>x.key===key);
document.getElementById('svc-icon').value = ic.svg;
document.getElementById('svc-icon-box').innerHTML = ic.svg;
document.getElementById('svc-icon-label').textContent = ic.label + ' ▾';
// close grid
document.getElementById('svc-icon-grid-wrap').classList.remove('open');
}
function toggleIconGrid() {
document.getElementById('svc-icon-grid-wrap').classList.toggle('open');
}
// ── Services ──────────────────────────────────────────────────────────────────
let svcs=[];
async function loadServices() {
svcs = await api('/api/services/all') || [];
document.getElementById('servicesTable').innerHTML = svcs.map(s=>`
<tr>
<td>${s.order}</td>
<td><strong>${s.title}</strong></td>
<td style="max-width:300px;color:var(--mid)">${s.description.substring(0,60)}...</td>
<td><span class="badge ${s.isActive?'badge-green':'badge-red'}">${s.isActive?'فعال':'غیرفعال'}</span></td>
<td><button class="btn btn-secondary btn-sm" onclick="editSvc(${s.id})">ویرایش</button> <button class="btn btn-danger btn-sm" onclick="deleteSvc(${s.id})">حذف</button></td>
</tr>`).join('');
}
function openSvcModal(id) {
document.getElementById('svcModalTitle').textContent = 'افزودن خدمت';
document.getElementById('svc-id').value='';
document.getElementById('svc-title').value='';
document.getElementById('svc-desc').value='';
document.getElementById('svc-order').value=svcs.length+1;
document.getElementById('svc-active').value='true';
document.getElementById('svc-before').value='';
document.getElementById('svc-after').value='';
showPreview('prev-svc-before','');
showPreview('prev-svc-after','');
document.getElementById('svc-icon-grid-wrap').classList.remove('open');
renderIconPicker(null);
document.getElementById('svcModal').classList.remove('hidden');
}
function editSvc(id) {
const s = svcs.find(x=>x.id===id);
document.getElementById('svcModalTitle').textContent = 'ویرایش خدمت';
document.getElementById('svc-id').value=s.id;
document.getElementById('svc-title').value=s.title;
document.getElementById('svc-desc').value=s.description;
document.getElementById('svc-order').value=s.order;
document.getElementById('svc-active').value=String(s.isActive);
document.getElementById('svc-before').value=s.beforeImageUrl||'';
document.getElementById('svc-after').value=s.afterImageUrl||'';
document.getElementById('svc-icon-grid-wrap').classList.remove('open');
renderIconPicker(s.iconSvg || null);
showPreview('prev-svc-before', s.beforeImageUrl||'');
showPreview('prev-svc-after', s.afterImageUrl||'');
document.getElementById('svcModal').classList.remove('hidden');
}
async function saveSvc() {
const id = document.getElementById('svc-id').value;
const body = {
title: document.getElementById('svc-title').value,
description: document.getElementById('svc-desc').value,
order: parseInt(document.getElementById('svc-order').value),
isActive: document.getElementById('svc-active').value==='true',
iconSvg: document.getElementById('svc-icon').value,
beforeImageUrl: document.getElementById('svc-before').value,
afterImageUrl: document.getElementById('svc-after').value
};
if (id) await api(`/api/services/${id}`,{method:'PUT',body:JSON.stringify(body)});
else await api('/api/services',{method:'POST',body:JSON.stringify(body)});
closeModal('svcModal');
toast('خدمت ذخیره شد ✓');
loadServices();
}
async function deleteSvc(id) {
if (!confirm('حذف شود؟')) return;
await api(`/api/services/${id}`,{method:'DELETE'});
toast('حذف شد','error');
loadServices();
}
// ── Image Upload Helper ───────────────────────────────────────────────────────
// ── Image Cropper ─────────────────────────────────────────────────────────────
const cropper = {
inputId: null, previewId: null,
img: null, naturalW: 0, naturalH: 0,
scale: 1, offsetX: 0, offsetY: 0,
ratio: 1, // 0 = free
box: { x:0, y:0, w:0, h:0 }, // in canvas-display px
drag: null // {type, startX,startY,box0}
};
function openCropper(inputId, previewId, file) {
cropper.inputId = inputId; cropper.previewId = previewId;
const reader = new FileReader();
reader.onload = e => {
const img = new Image();
img.onload = () => {
cropper.img = img;
cropper.naturalW = img.naturalWidth;
cropper.naturalH = img.naturalHeight;
document.getElementById('cropperModal').classList.remove('hidden');
requestAnimationFrame(() => initCropperCanvas());
};
img.src = e.target.result;
};
reader.readAsDataURL(file);
}
function initCropperCanvas() {
const stage = document.getElementById('cropperStage');
const canvas = document.getElementById('cropperCanvas');
const sw = stage.clientWidth, sh = stage.clientHeight || 400;
canvas.width = sw; canvas.height = sh;
// fit image
const scaleX = sw / cropper.naturalW, scaleY = sh / cropper.naturalH;
cropper.scale = Math.min(scaleX, scaleY);
const dw = cropper.naturalW * cropper.scale;
const dh = cropper.naturalH * cropper.scale;
cropper.offsetX = (sw - dw) / 2;
cropper.offsetY = (sh - dh) / 2;
// default crop = center square (or full if free)
const bSize = Math.min(dw, dh) * 0.8;
cropper.box = { x: cropper.offsetX + (dw - bSize)/2, y: cropper.offsetY + (dh - bSize)/2, w: bSize, h: bSize };
if (cropper.ratio !== 1) applyCropRatioToBox();
drawCropperCanvas();
positionCropBox();
document.getElementById('cropBox').classList.remove('hidden');
bindCropperEvents();
}
function drawCropperCanvas() {
const canvas = document.getElementById('cropperCanvas');
const ctx = canvas.getContext('2d');
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(cropper.img, cropper.offsetX, cropper.offsetY,
cropper.naturalW * cropper.scale, cropper.naturalH * cropper.scale);
}
function positionCropBox() {
const box = document.getElementById('cropBox');
const b = cropper.box;
box.style.cssText = `left:${b.x}px;top:${b.y}px;width:${b.w}px;height:${b.h}px;position:absolute;`;
}
function clampBox(b) {
const ox=cropper.offsetX, oy=cropper.offsetY;
const mw=cropper.naturalW*cropper.scale, mh=cropper.naturalH*cropper.scale;
b.w = Math.max(40, Math.min(b.w, mw)); b.h = Math.max(40, Math.min(b.h, mh));
b.x = Math.max(ox, Math.min(b.x, ox+mw-b.w));
b.y = Math.max(oy, Math.min(b.y, oy+mh-b.h));
return b;
}
function applyCropRatioToBox() {
if (!cropper.ratio) return;
const b = cropper.box;
b.h = b.w / cropper.ratio;
cropper.box = clampBox(b);
}
function setCropRatio(w, h) {
cropper.ratio = (w && h) ? w/h : 0;
document.querySelectorAll('.cropper-ratio-btn').forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
if (cropper.img) { applyCropRatioToBox(); positionCropBox(); }
}
function bindCropperEvents() {
const box = document.getElementById('cropBox');
const stage = document.getElementById('cropperStage');
// Handles resize
box.querySelectorAll('.cropper-handle').forEach(h => {
h.addEventListener('mousedown', ev => {
ev.preventDefault(); ev.stopPropagation();
cropper.drag = {type:'resize', handle:h.dataset.h, startX:ev.clientX, startY:ev.clientY, box0:{...cropper.box}};
});
h.addEventListener('touchstart', ev => {
ev.preventDefault(); ev.stopPropagation();
const t=ev.touches[0];
cropper.drag = {type:'resize', handle:h.dataset.h, startX:t.clientX, startY:t.clientY, box0:{...cropper.box}};
});
});
// Box drag move
box.addEventListener('mousedown', ev => {
if (ev.target.classList.contains('cropper-handle')) return;
ev.preventDefault();
cropper.drag = {type:'move', startX:ev.clientX, startY:ev.clientY, box0:{...cropper.box}};
});
box.addEventListener('touchstart', ev => {
if (ev.target.classList.contains('cropper-handle')) return;
ev.preventDefault();
const t=ev.touches[0];
cropper.drag = {type:'move', startX:t.clientX, startY:t.clientY, box0:{...cropper.box}};
});
const onMove = (cx, cy) => {
if (!cropper.drag) return;
const dx=cx-cropper.drag.startX, dy=cy-cropper.drag.startY;
const b0=cropper.drag.box0, r=cropper.ratio;
let nb={...b0};
if (cropper.drag.type==='move') {
nb.x=b0.x+dx; nb.y=b0.y+dy;
} else {
const h=cropper.drag.handle;
if (h==='se') { nb.w=Math.max(40,b0.w+dx); nb.h=r?nb.w/r:Math.max(40,b0.h+dy); }
else if (h==='sw') { nb.w=Math.max(40,b0.w-dx); nb.x=b0.x+b0.w-nb.w; nb.h=r?nb.w/r:Math.max(40,b0.h+dy); }
else if (h==='ne') { nb.w=Math.max(40,b0.w+dx); nb.h=r?nb.w/r:Math.max(40,b0.h-dy); nb.y=b0.y+b0.h-nb.h; }
else if (h==='nw') { nb.w=Math.max(40,b0.w-dx); nb.x=b0.x+b0.w-nb.w; nb.h=r?nb.w/r:Math.max(40,b0.h-dy); nb.y=b0.y+b0.h-nb.h; }
}
cropper.box=clampBox(nb); positionCropBox();
};
window.addEventListener('mousemove', ev => onMove(ev.clientX, ev.clientY));
window.addEventListener('touchmove', ev => { const t=ev.touches[0]; onMove(t.clientX,t.clientY); }, {passive:true});
window.addEventListener('mouseup', () => cropper.drag=null);
window.addEventListener('touchend', () => cropper.drag=null);
}
function closeCropper() {
document.getElementById('cropperModal').classList.add('hidden');
document.getElementById('cropBox').classList.add('hidden');
cropper.drag = null;
}
async function applyCrop() {
const b = cropper.box;
// Convert display px → natural image px
const sx = (b.x - cropper.offsetX) / cropper.scale;
const sy = (b.y - cropper.offsetY) / cropper.scale;
const sw = b.w / cropper.scale;
const sh = b.h / cropper.scale;
const out = document.createElement('canvas');
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);
// Capture targets BEFORE closing (closeCropper doesn't clear them, but be safe)
const _inputId = cropper.inputId, _previewId = cropper.previewId;
out.toBlob(async blob => {
closeCropper();
const inputId = _inputId, previewId = _previewId;
const btn = document.getElementById(`upload-btn-${inputId}`);
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.disabled = true;
try {
const fd = new FormData();
fd.append('file', new File([blob], 'crop.jpg', {type:'image/jpeg'}));
const r = await fetch('/api/upload', {method:'POST', headers:{'Authorization':`Bearer ${token}`}, body:fd});
if (!r.ok) throw new Error(await r.text());
const { url } = await r.json();
document.getElementById(inputId).value = url;
showPreview(previewId, url);
toast('تصویر با موفقیت آپلود شد ✓');
} catch(e) { toast('خطا در آپلود: '+e.message,'error'); }
finally { btn.innerHTML=orig; btn.disabled=false; }
}, 'image/jpeg', 0.92);
}
async function uploadImage(inputId, previewId) {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'image/jpeg,image/png,image/webp,image/gif';
fileInput.onchange = () => {
const file = fileInput.files[0];
if (!file) return;
openCropper(inputId, previewId, file);
};
fileInput.click();
}
function showPreview(previewId, url) {
const img = document.getElementById(previewId);
if (!img) return;
if (url) { img.src = url; img.style.display = 'block'; }
else { img.src = ''; img.style.display = 'none'; }
const rm = document.getElementById(previewId.replace('prev-', 'rm-'));
if (rm) rm.style.display = url ? 'inline-flex' : 'none';
}
function syncPreview(inputId, previewId) {
const val = (document.getElementById(inputId).value || '').trim();
showPreview(previewId, (val.startsWith('http') || val.startsWith('/')) ? val : '');
}
function removeImage(inputId, previewId) {
document.getElementById(inputId).value = '';
showPreview(previewId, '');
}
function clearGalleryPreviews() {
['prev-gal-img','prev-gal-before','prev-gal-after'].forEach(id=>showPreview(id,''));
}
// ── Gallery ───────────────────────────────────────────────────────────────────
let gals=[];
async function loadGallery() {
gals = await api('/api/gallery/all') || [];
document.getElementById('galleryTable').innerHTML = gals.map(g=>`
<tr>
<td><span class="badge badge-gold">${g.category||'—'}</span></td>
<td>${g.caption||'—'}</td>
<td><span class="badge ${g.isActive?'badge-green':'badge-red'}">${g.isActive?'فعال':'غیرفعال'}</span></td>
<td><button class="btn btn-secondary btn-sm" onclick="editGallery(${g.id})">ویرایش</button> <button class="btn btn-danger btn-sm" onclick="deleteGallery(${g.id})">حذف</button></td>
</tr>`).join('');
}
function openGalleryModal() {
['id','img','before','after','cat','caption'].forEach(k=>document.getElementById(`gal-${k}`).value='');
document.getElementById('gal-order').value=gals.length+1;
document.getElementById('gal-active').value='true';
clearGalleryPreviews();
document.getElementById('galleryModalTitle').textContent='افزودن تصویر';
document.getElementById('galleryModal').classList.remove('hidden');
}
function editGallery(id) {
const g=gals.find(x=>x.id===id);
document.getElementById('gal-id').value=g.id;
document.getElementById('gal-img').value=g.imageUrl||'';
document.getElementById('gal-before').value=g.beforeImageUrl||'';
document.getElementById('gal-after').value=g.afterImageUrl||'';
document.getElementById('gal-cat').value=g.category||'';
document.getElementById('gal-caption').value=g.caption||'';
document.getElementById('gal-order').value=g.order;
clearGalleryPreviews();
if(g.imageUrl) showPreview('prev-gal-img', g.imageUrl);
if(g.beforeImageUrl) showPreview('prev-gal-before', g.beforeImageUrl);
if(g.afterImageUrl) showPreview('prev-gal-after', g.afterImageUrl);
document.getElementById('galleryModalTitle').textContent='ویرایش تصویر';
document.getElementById('gal-active').value=String(g.isActive);
document.getElementById('galleryModal').classList.remove('hidden');
}
async function saveGallery() {
const id=document.getElementById('gal-id').value;
const body={imageUrl:document.getElementById('gal-img').value,beforeImageUrl:document.getElementById('gal-before').value,afterImageUrl:document.getElementById('gal-after').value,category:document.getElementById('gal-cat').value,caption:document.getElementById('gal-caption').value,order:parseInt(document.getElementById('gal-order').value),isActive:document.getElementById('gal-active').value==='true'};
if(id) await api(`/api/gallery/${id}`,{method:'PUT',body:JSON.stringify(body)});
else await api('/api/gallery',{method:'POST',body:JSON.stringify(body)});
closeModal('galleryModal'); toast('ذخیره شد ✓'); loadGallery();
}
async function deleteGallery(id){if(!confirm('حذف؟'))return;await api(`/api/gallery/${id}`,{method:'DELETE'});toast('حذف شد','error');loadGallery();}
// ── Testimonials ──────────────────────────────────────────────────────────────
let testims=[];
async function loadTestimonials() {
testims=await api('/api/testimonials/all')||[];
document.getElementById('testimonialsTable').innerHTML=testims.map(t=>`
<tr>
<td>${t.authorEmoji} ${t.authorName}</td>
<td style="max-width:250px">${t.text.substring(0,60)}...</td>
<td>${'★'.repeat(t.rating)}</td>
<td><span class="badge ${t.isActive?'badge-green':'badge-red'}">${t.isActive?'فعال':'غیرفعال'}</span></td>
<td><button class="btn btn-secondary btn-sm" onclick="editTestim(${t.id})">ویرایش</button> <button class="btn btn-danger btn-sm" onclick="deleteTestim(${t.id})">حذف</button></td>
</tr>`).join('');
}
function openTestimModal(){['id','name','emoji','text','date'].forEach(k=>document.getElementById(`testim-${k}`).value='');document.getElementById('testim-emoji').value='👩';document.getElementById('testim-rating').value=5;document.getElementById('testim-active').value='true';document.getElementById('testimModal').classList.remove('hidden');}
function editTestim(id){const t=testims.find(x=>x.id===id);document.getElementById('testim-id').value=t.id;document.getElementById('testim-name').value=t.authorName;document.getElementById('testim-emoji').value=t.authorEmoji;document.getElementById('testim-text').value=t.text;document.getElementById('testim-rating').value=t.rating;document.getElementById('testim-date').value=t.date;document.getElementById('testim-active').value=String(t.isActive);document.getElementById('testimModal').classList.remove('hidden');}
async function saveTestim(){const id=document.getElementById('testim-id').value;const body={authorName:document.getElementById('testim-name').value,authorEmoji:document.getElementById('testim-emoji').value,text:document.getElementById('testim-text').value,rating:parseInt(document.getElementById('testim-rating').value),date:document.getElementById('testim-date').value,isActive:document.getElementById('testim-active').value==='true'};if(id)await api(`/api/testimonials/${id}`,{method:'PUT',body:JSON.stringify(body)});else await api('/api/testimonials',{method:'POST',body:JSON.stringify(body)});closeModal('testimModal');toast('ذخیره شد ✓');loadTestimonials();}
async function deleteTestim(id){if(!confirm('حذف؟'))return;await api(`/api/testimonials/${id}`,{method:'DELETE'});toast('حذف شد','error');loadTestimonials();}
// ── Blog Categories ───────────────────────────────────────────────────────────
let cats=[];
async function loadCategories(){
cats=await api('/api/blog/categories')||[];
document.getElementById('categoriesTable').innerHTML=cats.map(c=>`<tr><td><strong>${c.name}</strong></td><td dir="ltr" style="color:var(--light)">${c.slug}</td><td><span class="badge badge-gold">${c.postCount}</span></td><td><button class="btn btn-secondary btn-sm" onclick="editCat(${c.id})">ویرایش</button> <button class="btn btn-danger btn-sm" onclick="deleteCat(${c.id})">حذف</button></td></tr>`).join('');
}
function openCatModal(){document.getElementById('catModalTitle').textContent='دسته جدید';['id','name','slug','desc'].forEach(k=>document.getElementById(`cat-${k}`).value='');document.getElementById('catModal').classList.remove('hidden');}
function editCat(id){const c=cats.find(x=>x.id===id);document.getElementById('catModalTitle').textContent='ویرایش دسته';document.getElementById('cat-id').value=c.id;document.getElementById('cat-name').value=c.name;document.getElementById('cat-slug').value=c.slug;document.getElementById('cat-desc').value=c.description;document.getElementById('catModal').classList.remove('hidden');}
async function saveCat(){const id=document.getElementById('cat-id').value;const body={name:document.getElementById('cat-name').value,slug:document.getElementById('cat-slug').value,description:document.getElementById('cat-desc').value};if(id)await api(`/api/blog/categories/${id}`,{method:'PUT',body:JSON.stringify(body)});else await api('/api/blog/categories',{method:'POST',body:JSON.stringify(body)});closeModal('catModal');toast('ذخیره شد ✓');loadCategories();}
async function deleteCat(id){if(!confirm('حذف؟'))return;await api(`/api/blog/categories/${id}`,{method:'DELETE'});toast('حذف شد','error');loadCategories();}
// ── Blog Posts ────────────────────────────────────────────────────────────────
let posts=[];
async function loadPosts(){
posts=await api('/api/blog/posts/admin')||[];
document.getElementById('postsTable').innerHTML=posts.map(p=>`
<tr>
<td><strong>${p.title}</strong></td>
<td><span class="badge badge-gold">${p.category?.name||'—'}</span></td>
<td style="color:var(--gold);font-size:.8rem">${p.focusKeyword||'—'}</td>
<td>${p.viewCount}</td>
<td><span class="badge ${p.isPublished?'badge-green':'badge-red'}">${p.isPublished?'منتشر':'پیش‌نویس'}</span></td>
<td>
<button class="btn btn-secondary btn-sm" onclick="editPost(${p.id})">ویرایش</button>
<a href="/blog/${p.slug}" target="_blank" class="btn btn-secondary btn-sm">مشاهده</a>
<button class="btn btn-danger btn-sm" onclick="deletePost(${p.id})">حذف</button>
</td>
</tr>`).join('');
}
async function openPostEditor(){
if(!cats.length) await loadCategories();
const catSel=document.getElementById('post-category');
catSel.innerHTML='<option value="">انتخاب دسته...</option>'+cats.map(c=>`<option value="${c.id}">${c.name}</option>`).join('');
document.getElementById('postModalTitle').textContent='مقاله جدید';
['id','title','slug','focus','metatitle','metadesc','keywords','image'].forEach(k=>document.getElementById(`post-${k}`).value='');
document.getElementById('post-content').innerHTML='';
showPreview('prev-post-image','');
document.getElementById('post-excerpt').value='';
document.getElementById('post-published').value='false';
document.getElementById('post-schematype').value='MedicalWebPage';
document.getElementById('post-category').value='';
updateLen('post-metatitle','mtLen',70);
updateLen('post-metadesc','mdLen',160);
document.getElementById('postModal').classList.remove('hidden');
}
async function editPost(id){
await openPostEditor();
const p=await api(`/api/blog/posts/id/${id}`);
if(!p)return;
document.getElementById('postModalTitle').textContent='ویرایش مقاله';
document.getElementById('post-id').value=p.id;
document.getElementById('post-title').value=p.title;
document.getElementById('post-slug').value=p.slug;
document.getElementById('post-excerpt').value=p.excerpt;
document.getElementById('post-content').innerHTML=p.content;
document.getElementById('post-focus').value=p.focusKeyword;
document.getElementById('post-metatitle').value=p.metaTitle;
document.getElementById('post-metadesc').value=p.metaDescription;
document.getElementById('post-keywords').value=p.keywords;
document.getElementById('post-image').value=p.featuredImage||'';
showPreview('prev-post-image', p.featuredImage||'');
document.getElementById('post-published').value=String(p.isPublished);
document.getElementById('post-schematype').value=p.articleType;
document.getElementById('post-category').value=p.categoryId||'';
updateLen('post-metatitle','mtLen',70);
updateLen('post-metadesc','mdLen',160);
checkSeo();
}
async function savePost(){
const id=document.getElementById('post-id').value;
const catId=document.getElementById('post-category').value;
const body={
title:document.getElementById('post-title').value,
slug:document.getElementById('post-slug').value,
excerpt:document.getElementById('post-excerpt').value,
content:document.getElementById('post-content').innerHTML,
featuredImage:document.getElementById('post-image').value,
author:'دکتر سوسن آل‌طه',
metaTitle:document.getElementById('post-metatitle').value,
metaDescription:document.getElementById('post-metadesc').value,
focusKeyword:document.getElementById('post-focus').value,
keywords:document.getElementById('post-keywords').value,
articleType:document.getElementById('post-schematype').value,
isPublished:document.getElementById('post-published').value==='true',
categoryId:catId?parseInt(catId):null,
ogImage:document.getElementById('post-image').value,
};
if(id) await api(`/api/blog/posts/${id}`,{method:'PUT',body:JSON.stringify(body)});
else await api('/api/blog/posts',{method:'POST',body:JSON.stringify(body)});
closeModal('postModal'); toast('مقاله ذخیره شد ✓'); loadPosts();
}
async function deletePost(id){if(!confirm('حذف مقاله؟'))return;await api(`/api/blog/posts/${id}`,{method:'DELETE'});toast('مقاله حذف شد','error');loadPosts();}
// ── FAQs ──────────────────────────────────────────────────────────────────────
let faqs=[];
async function loadFaqs(){
faqs=await api('/api/faqs/all')||[];
document.getElementById('faqsTable').innerHTML=faqs.map(f=>`
<tr>
<td>${f.order}</td>
<td style="max-width:360px"><strong>${esc(f.question)}</strong></td>
<td><span class="badge ${f.isActive?'badge-green':'badge-red'}">${f.isActive?'فعال':'غیرفعال'}</span></td>
<td><button class="btn btn-secondary btn-sm" onclick="editFaq(${f.id})">ویرایش</button> <button class="btn btn-danger btn-sm" onclick="deleteFaq(${f.id})">حذف</button></td>
</tr>`).join('');
}
function openFaqModal(){
document.getElementById('faqModalTitle').textContent='افزودن سوال';
document.getElementById('faq-id').value='';
document.getElementById('faq-question').value='';
document.getElementById('faq-answer').value='';
document.getElementById('faq-order').value=faqs.length+1;
document.getElementById('faq-active').value='true';
document.getElementById('faqModal').classList.remove('hidden');
}
function editFaq(id){
const f=faqs.find(x=>x.id===id);
document.getElementById('faqModalTitle').textContent='ویرایش سوال';
document.getElementById('faq-id').value=f.id;
document.getElementById('faq-question').value=f.question;
document.getElementById('faq-answer').value=f.answer;
document.getElementById('faq-order').value=f.order;
document.getElementById('faq-active').value=String(f.isActive);
document.getElementById('faqModal').classList.remove('hidden');
}
async function saveFaq(){
const id=document.getElementById('faq-id').value;
const body={question:document.getElementById('faq-question').value,answer:document.getElementById('faq-answer').value,order:parseInt(document.getElementById('faq-order').value),isActive:document.getElementById('faq-active').value==='true'};
if(id) await api(`/api/faqs/${id}`,{method:'PUT',body:JSON.stringify(body)});
else await api('/api/faqs',{method:'POST',body:JSON.stringify(body)});
closeModal('faqModal');toast('ذخیره شد ✓');loadFaqs();
}
async function deleteFaq(id){if(!confirm('این سوال حذف شود؟'))return;await api(`/api/faqs/${id}`,{method:'DELETE'});toast('حذف شد','error');loadFaqs();}
// ── SEO ───────────────────────────────────────────────────────────────────────
async function loadSeo(){
const s=await api('/api/seo/stats');
if(!s)return;
document.getElementById('seo-posts').textContent=s.total;
document.getElementById('seo-views').textContent=s.views;
document.getElementById('seo-nometa').textContent=s.noMeta;
document.getElementById('seoTopPosts').innerHTML=s.topPosts.map(p=>`<tr><td>${p.title}</td><td><span class="badge badge-gold">${p.viewCount}</span></td></tr>`).join('');
}
// ── SEO helpers ───────────────────────────────────────────────────────────────
function autoSlug(){
const t=document.getElementById('post-title').value;
const s=t.trim().replace(/\s+/g,'-').replace(/[^؀-ۿa-z0-9\-]/g,'').toLowerCase();
document.getElementById('post-slug').value=s;
checkSeo();
}
function updateLen(inputId,labelId,max){
const el=document.getElementById(inputId);
const len=el.value.length;
const label=document.getElementById(labelId);
label.textContent=`(${len}/${max})`;
label.style.color=len>max?'var(--danger)':(len>max*.8?'var(--gold)':'var(--light)');
}
function checkSeo(){
const kw=document.getElementById('post-focus').value.trim();
const title=document.getElementById('post-title').value;
const mt=document.getElementById('post-metatitle').value;
const md=document.getElementById('post-metadesc').value;
const fb=document.getElementById('seoFeedback');
if(!kw){fb.innerHTML='';return;}
const checks=[
{ok:title.includes(kw), msg:'کلیدواژه در عنوان'},
{ok:mt.includes(kw), msg:'کلیدواژه در Meta Title'},
{ok:md.includes(kw), msg:'کلیدواژه در Meta Description'},
{ok:mt.length<=70&&mt.length>0, msg:'طول Meta Title مناسب'},
{ok:md.length<=160&&md.length>100, msg:'طول Meta Description مناسب'},
];
const pass=checks.filter(c=>c.ok).length;
const cls=pass>=4?'good':pass>=2?'ok':'bad';
const emoji=pass>=4?'✅':pass>=2?'⚠️':'❌';
fb.innerHTML=`<div class="seo-score ${cls}">${emoji} امتیاز SEO: ${pass}/${checks.length}${checks.map(c=>`<span style="opacity:${c.ok?1:.4}">${c.ok?'✓':'✗'} ${c.msg}</span>`).join(' | ')}</div>`;
}
// ── Editor helpers ────────────────────────────────────────────────────────────
function fmt(cmd){document.getElementById('post-content').focus();document.execCommand(cmd);}
function fmtBlock(tag){document.getElementById('post-content').focus();document.execCommand('formatBlock',false,tag);}
function insLink(){const url=prompt('آدرس لینک:');if(url){document.getElementById('post-content').focus();document.execCommand('createLink',false,url);}}
// ── Modal helpers ─────────────────────────────────────────────────────────────
function closeModal(id){document.getElementById(id).classList.add('hidden');}
document.querySelectorAll('.modal-overlay').forEach(m=>m.addEventListener('click',e=>{if(e.target===m)m.classList.add('hidden');}));
// ── File Manager ──────────────────────────────────────────────────────────────
let _fmTarget = null; // {inputId, previewId}
let _fmFiles = [];
function openFileMgr(inputId, previewId) {
_fmTarget = {inputId, previewId};
document.getElementById('fileMgr').classList.remove('hidden');
document.getElementById('fm-search').value = '';
loadFmFiles();
}
function closeFileMgr() {
document.getElementById('fileMgr').classList.add('hidden');
_fmTarget = null;
}
async function loadFmFiles() {
const grid = document.getElementById('fm-grid');
grid.innerHTML = '<div class="fm-loading"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:32px;height:32px;animation:spin .8s linear infinite"><path d="M21 12a9 9 0 1 1-6.219-8.56"/></svg>در حال بارگذاری...</div>';
try {
const r = await fetch('/api/upload', {headers:{Authorization:`Bearer ${token}`}});
if (!r.ok) throw new Error('HTTP ' + r.status);
_fmFiles = await r.json();
renderFmGrid(_fmFiles);
} catch(e) {
grid.innerHTML = `<div class="fm-empty" style="color:var(--danger)">⚠️ خطا در بارگذاری فایل‌ها<br><small style="color:var(--mid)">${e.message}</small><br><button class="btn btn-secondary btn-sm" style="margin-top:1rem" onclick="loadFmFiles()">تلاش مجدد</button></div>`;
}
}
function renderFmGrid(files) {
const cur = _fmTarget ? document.getElementById(_fmTarget.inputId)?.value : '';
const grid = document.getElementById('fm-grid');
if (!files.length) {
grid.innerHTML = '<div class="fm-empty">هنوز هیچ فایلی آپلود نشده است<br><small>از دکمه «آپلود جدید» استفاده کنید</small></div>';
return;
}
grid.innerHTML = `<div class="fm-grid">${files.map(f=>`
<div class="fm-tile${cur===f.url?' fm-selected':''}" data-url="${f.url}" onclick="selectFmFile('${f.url}')">
<div class="fm-thumb" style="background-image:url('${f.url}')"></div>
<div class="fm-info">
<span class="fm-name">${f.name}</span>
<span class="fm-meta">${fmBytes(f.size)}</span>
</div>
<div class="fm-actions" onclick="event.stopPropagation()">
<button class="btn btn-primary btn-sm" onclick="selectFmFile('${f.url}')">انتخاب</button>
<button class="btn btn-danger btn-sm" onclick="deleteFmFile('${f.name}',this)">حذف</button>
</div>
</div>`).join('')}</div>`;
}
function filterFmFiles() {
const q = document.getElementById('fm-search').value.trim().toLowerCase();
renderFmGrid(q ? _fmFiles.filter(f=>f.name.toLowerCase().includes(q)) : _fmFiles);
}
function selectFmFile(url) {
if (!_fmTarget) return;
document.getElementById(_fmTarget.inputId).value = url;
showPreview(_fmTarget.previewId, url);
closeFileMgr();
toast('تصویر انتخاب شد ✓');
}
async function deleteFmFile(name, btnEl) {
if (!confirm(`فایل "${name}" برای همیشه حذف شود؟`)) return;
try {
const r = await fetch(`/api/upload/${encodeURIComponent(name)}`, {
method:'DELETE', headers:{Authorization:`Bearer ${token}`}
});
if (!r.ok) throw new Error(r.status);
_fmFiles = _fmFiles.filter(f=>f.name!==name);
btnEl.closest('.fm-tile').remove();
// clear the target input if it was pointing at the deleted file
if (_fmTarget) {
const el = document.getElementById(_fmTarget.inputId);
if (el && el.value.endsWith('/'+name)) {
el.value = '';
showPreview(_fmTarget.previewId, '');
}
}
if (!_fmFiles.length) renderFmGrid([]);
toast('فایل حذف شد ✓');
} catch(e) { toast('خطا در حذف فایل', 'error'); }
}
async function fmUploadNew() {
const fi = document.createElement('input');
fi.type='file'; fi.accept='image/jpeg,image/png,image/webp,image/gif';
fi.onchange = async () => {
const file = fi.files[0]; if (!file) return;
const btn = document.getElementById('fm-upload-btn');
btn.disabled=true; btn.textContent='در حال آپلود...';
try {
const fd=new FormData(); fd.append('file',file);
const r=await fetch('/api/upload',{method:'POST',headers:{Authorization:`Bearer ${token}`},body:fd});
if(!r.ok) throw new Error(await r.text());
await loadFmFiles();
toast('تصویر آپلود شد ✓');
} catch(e){ toast('خطا در آپلود: '+e.message,'error'); }
finally{ btn.disabled=false; btn.textContent='+ آپلود جدید'; }
};
fi.click();
}
function fmBytes(b){
if(b<1024) return b+' B';
if(b<1048576) return (b/1024).toFixed(1)+' KB';
return (b/1048576).toFixed(1)+' MB';
}
// ── Auto-inject 📁 button next to every upload button ────────────────────────
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('.upload-btn').forEach(btn => {
const m = btn.id.match(/^upload-btn-(.+)$/);
if (!m) return;
const inputId = m[1], previewId = `prev-${inputId}`;
const fb = document.createElement('button');
fb.type='button'; fb.className='fm-open-btn'; fb.title='انتخاب از فایل‌ها';
fb.innerHTML='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>';
fb.onclick = () => openFileMgr(inputId, previewId);
btn.insertAdjacentElement('afterend', fb);
});
});
// ── Init ──────────────────────────────────────────────────────────────────────
async function init(){
if(!token){return;}
document.getElementById('loginScreen').classList.add('hidden');
loadDashboard();
loadCategories();
loadComments(); // populate pending badge on load
loadHealthRequests(); // populate health requests badge on load
}
// Auto-login if token exists
if(token) init();
</script>
<!-- ── Image Cropper Modal ──────────────────────────────────────────────────── -->
<div class="cropper-overlay hidden" id="cropperModal">
<div class="cropper-modal">
<div class="cropper-head">
<span class="cropper-head-title">✂️ برش تصویر</span>
<div class="cropper-ratio-btns">
<button class="cropper-ratio-btn active" onclick="setCropRatio(1,1)" title="مربع">۱:۱</button>
<button class="cropper-ratio-btn" onclick="setCropRatio(4,3)" title="افقی">۴:۳</button>
<button class="cropper-ratio-btn" onclick="setCropRatio(16,9)" title="پانورامیک">۱۶:۹</button>
<button class="cropper-ratio-btn" onclick="setCropRatio(3,4)" title="عمودی">۳:۴</button>
<button class="cropper-ratio-btn" onclick="setCropRatio(0,0)" title="آزاد">آزاد</button>
</div>
<button class="modal-close" style="color:#ccc" onclick="closeCropper()"></button>
</div>
<div class="cropper-stage" id="cropperStage">
<canvas id="cropperCanvas"></canvas>
<div class="cropper-box hidden" id="cropBox">
<div class="cropper-handle nw" data-h="nw"></div>
<div class="cropper-handle ne" data-h="ne"></div>
<div class="cropper-handle sw" data-h="sw"></div>
<div class="cropper-handle se" data-h="se"></div>
</div>
</div>
<div class="cropper-foot">
<span class="cropper-hint">برای تنظیم ناحیه برش بکشید یا گوشه‌ها را تغییر دهید</span>
<button class="btn btn-secondary btn-sm" onclick="closeCropper()">انصراف</button>
<button class="btn btn-primary btn-sm" onclick="applyCrop()">✓ برش و آپلود</button>
</div>
</div>
</div>
<!-- ── File Manager Modal ─────────────────────────────────────────────────── -->
<div class="fm-overlay hidden" id="fileMgr" onclick="if(event.target===this)closeFileMgr()">
<div class="fm-modal">
<div class="fm-head">
<span class="fm-head-title">📁 مدیریت فایل‌ها</span>
<input class="fm-search" id="fm-search" type="text" placeholder="جستجو..." oninput="filterFmFiles()"/>
<button id="fm-upload-btn" class="btn btn-primary btn-sm" onclick="fmUploadNew()">+ آپلود جدید</button>
<button class="modal-close" onclick="closeFileMgr()"></button>
</div>
<div class="fm-body">
<div id="fm-grid"></div>
</div>
</div>
</div>
</body>
</html>