Files
xReleaseAsistant/BKP/14050626/xDependencies_1.4.31.html
T
2026-09-17 21:27:27 +03:30

402 lines
144 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>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
:root{--primary:#1a365d;--primary-light:#2c5282;--primary-dark:#0f2341;--accent:#c9a961;--accent-dark:#a8893f;--accent-light:#e0c789;--bg:#f5f7fa;--board-bg:#e2e8f0;--card-bg:#fff;--panel-bg:#fff;--text:#1a202c;--text-muted:#4a5568;--text-light:#718096;--border:#cbd5e0;--border-light:#e2e8f0;--success:#38a169;--danger:#e53e3e;--warning:#dd6b20;--info:#3182ce;--shadow:0 4px 6px rgba(0,0,0,.08),0 10px 20px rgba(26,54,93,.1);--shadow-sm:0 2px 4px rgba(0,0,0,.06);--shadow-lg:0 10px 30px rgba(0,0,0,.15);--radius:10px;--radius-sm:6px}
[data-theme="dark"]{--primary:#2c5282;--primary-light:#3b6fb5;--primary-dark:#1a365d;--accent:#e0c789;--accent-dark:#c9a961;--accent-light:#f0d9a0;--bg:#1a202c;--board-bg:#2d3748;--card-bg:#2d3748;--panel-bg:#1f2937;--text:#e2e8f0;--text-muted:#cbd5e0;--text-light:#a0aec0;--border:#4a5568;--border-light:#374151;--shadow:0 4px 6px rgba(0,0,0,.3),0 10px 20px rgba(0,0,0,.4);--shadow-sm:0 2px 4px rgba(0,0,0,.2);--shadow-lg:0 10px 30px rgba(0,0,0,.5)}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Vazirmatn',Tahoma,sans-serif;background:var(--bg);color:var(--text);height:100vh;overflow:hidden;font-size:14px;display:flex;flex-direction:column;transition:background .3s,color .3s}
.app-header{background:linear-gradient(135deg,var(--primary) 0%,var(--primary-light) 100%);color:#fff;padding:0 20px;height:64px;display:flex;align-items:center;justify-content:space-between;border-bottom:3px solid var(--accent);box-shadow:var(--shadow);z-index:100;flex-shrink:0}
.app-brand{display:flex;align-items:center;gap:14px}
.app-logo{width:44px;height:44px;background:var(--accent);border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:22px;font-weight:800;color:var(--primary-dark)}
.app-brand-text{display:flex;flex-direction:column;gap:2px}
.app-title{font-size:16px;font-weight:800;color:#fff}
.app-subtitle{font-size:11px;opacity:.85;color:rgba(255,255,255,.9)}
.btn{background:linear-gradient(135deg,var(--primary-light),var(--primary));color:#fff;border:none;padding:8px 14px;border-radius:var(--radius-sm);cursor:pointer;font-family:inherit;font-size:13px;font-weight:600;display:inline-flex;align-items:center;gap:6px;transition:all .2s;white-space:nowrap}
.btn:hover{transform:translateY(-1px);filter:brightness(1.1)}
.btn-accent{background:linear-gradient(135deg,var(--accent),var(--accent-dark));color:var(--primary-dark);font-weight:700}
.btn-ghost{background:rgba(26,54,93,.08);border:1px solid var(--border);color:var(--primary)}
[data-theme="dark"] .btn-ghost{background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.25);color:#fff}
.btn-danger{background:linear-gradient(135deg,var(--danger),#c53030);color:#fff}
.btn-sm{padding:4px 10px;font-size:11px}
.btn-xs{padding:2px 6px;font-size:10px}
.app-main{flex:1;display:flex;overflow:hidden}
.sidebar{width:280px;background:var(--panel-bg);border-right:1px solid var(--border);display:flex;flex-direction:column;transition:width .3s;overflow:hidden;flex-shrink:0;z-index:50}
.sidebar.collapsed{width:0}
.sidebar-header{background:linear-gradient(135deg,var(--primary),var(--primary-light));color:#fff;padding:12px;display:flex;align-items:center;justify-content:space-between;border-bottom:2px solid var(--accent);min-height:48px}
.sidebar-title{font-size:13px;font-weight:700;display:flex;align-items:center;gap:8px}
.sidebar-body{flex:1;overflow-y:auto;padding:8px}
.sidebar-group{margin-bottom:6px;border:1px solid var(--border-light);border-radius:var(--radius-sm);overflow:hidden;background:var(--card-bg)}
.sidebar-group-header{background:linear-gradient(135deg,var(--primary),var(--primary-light));color:#fff;padding:10px 12px;cursor:pointer;display:flex;align-items:center;justify-content:space-between;font-size:13px;font-weight:700;user-select:none}
.sidebar-group-header .group-icon{display:flex;align-items:center;gap:8px;flex:1}
.sidebar-group-header .group-arrow{transition:transform .3s;font-size:12px}
.sidebar-group.collapsed .group-arrow{transform:rotate(90deg)}
.sidebar-group.collapsed .sidebar-group-body{display:none}
.sidebar-group-body{padding:6px;display:flex;flex-direction:column;gap:4px}
.sidebar-btn{background:var(--card-bg);border:1px solid var(--border-light);color:var(--text);padding:8px 10px;border-radius:var(--radius-sm);cursor:pointer;font-family:inherit;font-size:12px;font-weight:500;display:flex;align-items:center;gap:8px;text-align:right;width:100%}
.sidebar-btn:hover{background:linear-gradient(135deg,var(--primary),var(--primary-light));color:#fff}
.tabs-bar{background:linear-gradient(135deg,#0f1724 0%,#1a2a44 100%);border-bottom:2px solid var(--accent);display:flex;align-items:center;padding:0 12px;min-height:48px;gap:10px;flex-wrap:wrap}
.toolbar-controls{display:flex;gap:6px;margin-left:8px}
.toolbar-icon-btn{background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.15);color:#fff;width:34px;height:34px;border-radius:6px;cursor:pointer;font-size:15px;display:flex;align-items:center;justify-content:center}
.toolbar-icon-btn:hover:not(:disabled){background:var(--accent);color:var(--primary-dark)}
.toolbar-search{flex:1;min-width:180px;max-width:320px;position:relative}
.search-input{width:100%;padding:7px 32px 7px 12px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.15);border-radius:6px;color:#fff;font-family:inherit;font-size:13px}
.search-input::placeholder{color:rgba(255,255,255,.5)}
.toolbar-filters{display:flex;gap:8px}
.filter-dropdown{display:flex;align-items:center;gap:6px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.15);border-radius:6px;padding:0 10px}
.filter-select{background:transparent;border:none;color:#fff;font-family:inherit;font-size:12px;font-weight:600;padding:7px 4px;cursor:pointer;outline:none;min-width:110px}
.filter-select option{background:var(--primary-dark);color:#fff}
.toolbar-stats{display:flex;gap:12px;margin-right:auto;padding-right:8px;border-right:1px solid rgba(255,255,255,.1)}
.stat-item{display:flex;align-items:center;gap:4px;font-size:11px;color:rgba(255,255,255,.85)}
.stat-value{font-weight:800;color:var(--accent);background:rgba(201,169,97,.15);padding:2px 7px;border-radius:10px;font-size:11px}
.board-area{flex:1;overflow:auto;padding:24px;background:var(--board-bg)}
.board-section{margin-bottom:32px}
.board-section.collapsed .section-content{display:none}
.board-section.collapsed .collapse-icon{transform:rotate(90deg)}
.collapse-icon{transition:transform .3s;font-size:14px;display:inline-block;margin-left:6px}
.board-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;padding-bottom:12px;border-bottom:2px solid var(--border)}
.board-header-title{font-size:18px;font-weight:800;color:var(--primary);display:flex;align-items:center;gap:10px;cursor:pointer;user-select:none}
[data-theme="dark"] .board-header-title{color:var(--accent)}
.board-header-actions{display:flex;gap:8px}
.projects-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:20px}
.project-card{background:var(--card-bg);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow-sm);transition:all .25s;display:flex;flex-direction:column}
.project-card:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg);border-color:var(--accent)}
.project-card-header{background:linear-gradient(135deg,var(--primary) 0%,var(--primary-light) 100%);color:#fff;padding:16px}
.project-card-title-row{display:flex;align-items:center;gap:10px;margin-bottom:4px}
.project-card-title{font-size:16px;font-weight:700;flex:1}
.project-card-desc{font-size:12px;opacity:.9;line-height:1.5}
.project-card-body{padding:14px 16px;flex:1;display:flex;flex-direction:column;gap:10px}
.project-stat-row{display:flex;justify-content:space-between;font-size:12px;color:var(--text-muted)}
.project-stat-value{font-weight:700;color:var(--primary)}
[data-theme="dark"] .project-stat-value{color:var(--accent)}
.project-type-badges{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
.type-badge{font-size:10px;font-weight:700;padding:2px 8px;border-radius:4px;display:inline-block}
.type-backend{background:#ebf4ff;color:#2b6cb0}.type-frontend{background:#f0fff4;color:#276749}.type-service{background:#fefcbf;color:#975a16}.type-database{background:#fed7e2;color:#97266d}.type-mobile{background:#e9d8fd;color:#553c9a}.type-infrastructure{background:#feebc8;color:#9c4221}
.project-card-footer{padding:12px 16px;background:var(--border-light);border-top:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;gap:6px}
.card-actions{display:flex;gap:6px}
.env-card-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}
.env-card-item{background:var(--card-bg);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow-sm);display:flex;flex-direction:column}
.env-card-preview{height:60px;display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:700;color:#fff}
.env-card-body{padding:14px 16px;flex:1;display:flex;flex-direction:column;gap:8px}
.env-card-name{font-size:15px;font-weight:700}
.env-card-desc{font-size:12px;color:var(--text-muted)}
.env-card-footer{padding:10px 16px;background:var(--border-light);border-top:1px solid var(--border);display:flex;gap:6px}
.board-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text-muted);text-align:center;padding:40px}
.board-empty-icon{font-size:80px;opacity:.3;margin-bottom:20px}
.board-empty h2{font-size:20px;color:var(--primary);margin-bottom:10px}
[data-theme="dark"] .board-empty h2{color:var(--accent)}
.no-results{text-align:center;padding:40px;color:var(--text-muted);background:var(--border-light);border-radius:var(--radius);margin:20px 0}
.modal-overlay{position:fixed;inset:0;background:rgba(26,54,93,.6);backdrop-filter:blur(3px);display:none;align-items:center;justify-content:center;z-index:1000;padding:20px}
.modal-overlay.active{display:flex}
.modal{background:var(--panel-bg);color:var(--text);border-radius:12px;width:100%;max-width:600px;max-height:90vh;overflow:hidden;display:flex;flex-direction:column;box-shadow:var(--shadow-lg)}
.modal-lg{max-width:800px}
.modal-xl{max-width:1000px}
.modal-galaxy{max-width:1200px;width:100%;height:85vh;max-height:85vh;border-radius:16px}
.modal-header{background:linear-gradient(135deg,var(--primary),var(--primary-light));color:#fff;padding:16px 20px;display:flex;align-items:center;justify-content:space-between;border-bottom:3px solid var(--accent);flex-wrap:wrap;gap:10px}
.modal-title{font-size:16px;font-weight:700;display:flex;align-items:center;gap:10px}
.modal-close{background:rgba(255,255,255,.15);border:none;color:#fff;width:30px;height:30px;border-radius:50%;cursor:pointer;font-size:16px}
.modal-close:hover{background:var(--danger)}
.modal-body{padding:20px;overflow-y:auto;flex:1}
.modal-footer{padding:14px 20px;border-top:1px solid var(--border);display:flex;justify-content:flex-start;gap:8px;background:var(--border-light);align-items:center;flex-wrap:wrap}
.detail-section{margin-bottom:20px;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden}
.detail-section-header{background:linear-gradient(135deg,var(--primary),var(--primary-light));color:#fff;padding:12px 16px;font-size:14px;font-weight:700;display:flex;align-items:center;justify-content:space-between}
.detail-section-header-actions{display:flex;align-items:center;gap:8px}
.detail-section-body{padding:16px;background:var(--card-bg)}
.detail-info-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:12px}
.detail-info-item{display:flex;flex-direction:column;gap:4px}
.detail-info-label{font-size:11px;color:var(--text-muted);font-weight:600}
.detail-info-value{font-size:13px;font-weight:600}
.subproject-list{display:flex;flex-direction:column;gap:10px}
.subproject-card{border:1px solid var(--border);border-radius:var(--radius-sm);padding:12px;background:var(--border-light)}
.subproject-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;flex-wrap:wrap;gap:8px}
.subproject-title{font-size:14px;font-weight:700;display:flex;align-items:center;gap:8px}
.subproject-actions{display:flex;gap:4px}
.subproject-meta{font-size:12px;color:var(--text-muted);margin-bottom:8px}
.deployment-section{margin-top:10px;padding-top:10px;border-top:1px dashed var(--border)}
.deployment-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}
.deployment-header-title{font-size:11px;font-weight:600;color:var(--text-muted)}
.deployment-list{display:flex;flex-direction:column;gap:6px}
.deployment-item{display:flex;align-items:center;justify-content:space-between;padding:8px 10px;background:var(--card-bg);border:1px solid var(--border-light);border-radius:6px;font-size:11px;flex-wrap:wrap;gap:6px}
.deployment-item-info{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.deployment-env-tag{display:inline-flex;align-items:center;gap:4px;font-size:10px;padding:2px 8px;border-radius:4px;font-weight:600}
.deployment-version{font-family:'Vazirmatn',monospace;font-weight:700;color:var(--primary);font-size:11px}
[data-theme="dark"] .deployment-version{color:var(--accent)}
.deployment-date{font-size:10px;color:var(--text-muted)}
.deployment-actions{display:flex;gap:4px}
.deployment-notes{font-size:10px;color:var(--text-muted);width:100%;margin-top:4px;padding-top:4px;border-top:1px dashed var(--border-light);line-height:1.5}
.deployment-req{font-size:10px;color:var(--info);width:100%;margin-top:2px}
.empty-deployments{text-align:center;padding:12px;color:var(--text-muted);font-size:11px;background:var(--border-light);border-radius:6px}
.empty-subprojects{text-align:center;padding:30px;color:var(--text-muted);background:var(--border-light);border-radius:var(--radius-sm)}
.form-group{margin-bottom:16px}
.form-label{display:block;font-size:13px;font-weight:600;color:var(--primary);margin-bottom:6px}
[data-theme="dark"] .form-label{color:var(--accent-light)}
.form-label .required{color:var(--danger)}
.form-input,.form-textarea,.form-select{width:100%;padding:9px 12px;border:1px solid var(--border);border-radius:var(--radius-sm);font-family:inherit;font-size:13px;background:var(--card-bg);color:var(--text)}
.form-input:focus,.form-textarea:focus,.form-select:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(201,169,97,.15)}
.form-textarea{resize:vertical;min-height:70px;line-height:1.6}
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.form-hint{font-size:11px;color:var(--text-muted);margin-top:4px}
.form-error{font-size:11px;color:var(--danger);margin-top:4px;display:none}
.form-error.visible{display:block}
.color-input-wrapper{display:flex;align-items:center;gap:8px;padding:6px 10px;background:var(--card-bg);border:1px solid var(--border);border-radius:var(--radius-sm)}
.color-input-wrapper input[type="color"]{width:32px;height:32px;border:none;border-radius:4px;cursor:pointer;padding:0}
.color-preview-text{font-size:11px;font-family:monospace;color:var(--text-muted)}
.webhook-section{background:var(--border-light);border:1px solid var(--border);border-radius:var(--radius-sm);padding:14px;margin-top:12px}
.webhook-section-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
.webhook-section-title{font-size:13px;font-weight:700;color:var(--primary);display:flex;align-items:center;gap:6px}
.webhook-toggle{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--text-muted)}
.switch{position:relative;display:inline-block;width:36px;height:20px}
.switch input{opacity:0;width:0;height:0}
.slider{position:absolute;cursor:pointer;inset:0;background:var(--border);transition:.3s;border-radius:20px}
.slider::before{position:absolute;content:"";height:14px;width:14px;right:3px;bottom:3px;background:#fff;transition:.3s;border-radius:50%}
input:checked+.slider{background:var(--success)}
input:checked+.slider::before{transform:translateX(-16px)}
.webhook-status{display:flex;align-items:center;gap:8px;margin-top:10px;padding:8px 10px;background:var(--card-bg);border-radius:4px;font-size:11px;color:var(--text-muted)}
.webhook-status-dot{width:8px;height:8px;border-radius:50%;background:var(--text-muted)}
.webhook-status-dot.success{background:var(--success)}
.webhook-status-dot.error{background:var(--danger)}
.toast-container{position:fixed;bottom:20px;left:20px;z-index:2000;display:flex;flex-direction:column;gap:8px}
.toast{background:var(--primary);color:#fff;padding:12px 18px;border-radius:8px;box-shadow:var(--shadow-lg);font-size:13px;display:flex;align-items:center;gap:10px;min-width:250px}
.toast.success{background:var(--success)}
.toast.error{background:var(--danger)}
.toast.warning{background:var(--warning)}
.app-footer{background:linear-gradient(135deg,var(--primary),var(--primary-light));color:#fff;padding:8px 20px;font-size:11px;display:flex;justify-content:space-between;align-items:center;border-top:2px solid var(--accent);flex-shrink:0}
.footer-brand{color:var(--accent);font-weight:700}
.form-input[readonly],.form-textarea[readonly]{background:var(--border-light);color:var(--text-muted);cursor:not-allowed;opacity:.85;border-style:dashed}
.readonly-badge{display:inline-flex;align-items:center;gap:4px;font-size:10px;font-weight:600;color:var(--text-muted);background:var(--border-light);border:1px solid var(--border);padding:2px 8px;border-radius:10px;margin-right:6px}
.readonly-badge::before{content:'🔒';font-size:10px}
.readonly-info{background:linear-gradient(to left,#ebf8ff,#e6f6ff);border:1px solid #90cdf4;border-right:4px solid var(--info);padding:10px 14px;border-radius:6px;font-size:11px;color:var(--text-muted);margin-bottom:14px;display:flex;align-items:center;gap:8px}
.dependency-list{display:flex;flex-direction:column;gap:8px;margin-bottom:20px;max-height:200px;overflow-y:auto}
.dependency-item{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;background:var(--card-bg);border:1px solid var(--border);border-radius:var(--radius-sm)}
.dependency-info{display:flex;align-items:center;gap:10px;flex:1}
.dependency-project-name{font-weight:600;font-size:13px}
.dependency-search-box{margin-top:16px;padding-top:16px;border-top:1px solid var(--border)}
.dependency-search-input{width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:var(--radius-sm);font-family:inherit;font-size:13px;background:var(--card-bg);color:var(--text);margin-bottom:12px}
.dependency-search-results{max-height:300px;overflow-y:auto}
.dependency-project-result{margin-bottom:8px;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden}
.dependency-project-add-btn{padding:10px 12px;background:var(--card-bg);cursor:pointer;display:flex;align-items:center;gap:8px;font-size:13px;font-weight:600;width:100%;border:none;font-family:inherit;text-align:right}
.dependency-project-add-btn:hover{background:var(--accent-light)}
.empty-dependencies{text-align:center;padding:20px;color:var(--text-muted);font-size:12px;background:var(--border-light);border-radius:var(--radius-sm)}
.dependency-section-title{font-size:13px;font-weight:700;color:var(--primary);margin-bottom:10px;display:flex;align-items:center;gap:6px}
[data-theme="dark"] .dependency-section-title{color:var(--accent)}
.env-color-preview{padding:10px;border-radius:6px;text-align:center;font-weight:700;border:1px solid var(--border)}
.env-read-only-tag{display:inline-flex;align-items:center;gap:4px;font-size:12px;font-weight:600;padding:6px 10px;border-radius:6px}
.req-deployment-box{border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px;margin-bottom:8px;background:var(--border-light)}
.req-deployment-title{font-size:12px;font-weight:700;margin-bottom:6px;display:flex;align-items:center;gap:6px}
.req-deployment-select{width:100%;padding:8px;border:1px solid var(--border);border-radius:var(--radius-sm);font-family:inherit;font-size:12px;background:var(--card-bg);color:var(--text)}
.galaxy-toolbar{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
.galaxy-toolbar .btn{background:rgba(255,255,255,.12);border:1px solid rgba(255,255,255,.2);color:#fff;font-size:11px;padding:5px 10px}
.galaxy-toolbar .btn:hover{background:var(--accent);color:var(--primary-dark)}
.galaxy-modal-body{padding:0;position:relative;overflow:hidden;background:#0a0e27}
.galaxy-chart-container{width:100%;height:100%;min-height:400px;position:relative}
.galaxy-chart-container svg{width:100%;height:100%;display:block;cursor:grab}
.galaxy-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:rgba(255,255,255,.5);text-align:center;font-size:16px;gap:10px}
.galaxy-legend{position:absolute;bottom:16px;right:16px;background:rgba(10,14,39,.85);border:1px solid rgba(201,169,97,.3);border-radius:10px;padding:12px 16px;color:#fff;font-size:11px;z-index:10;max-width:220px}
.galaxy-legend-title{font-weight:700;font-size:12px;margin-bottom:10px;color:var(--accent-light)}
.galaxy-legend-item{display:flex;align-items:center;gap:8px;margin-bottom:6px}
.galaxy-legend-color{width:14px;height:14px;border-radius:50%;box-shadow:0 0 8px currentColor}
.galaxy-info-panel{position:absolute;top:16px;left:16px;background:rgba(10,14,39,.9);border:1px solid rgba(201,169,97,.3);border-radius:10px;padding:14px 18px;color:#fff;font-size:12px;z-index:10;max-width:280px;display:none}
.galaxy-info-panel.visible{display:block}
.galaxy-info-panel h4{font-size:14px;font-weight:700;color:var(--accent-light);margin-bottom:10px}
.galaxy-info-row{display:flex;justify-content:space-between;padding:4px 0;border-bottom:1px solid rgba(255,255,255,.1);gap:12px}
.galaxy-info-label{color:rgba(255,255,255,.6);font-size:11px}
.galaxy-info-value{font-weight:600;font-size:11px;text-align:left}
.galaxy-tooltip{position:fixed;background:rgba(10,14,39,.95);border:1px solid var(--accent);border-radius:8px;padding:10px 14px;color:#fff;font-size:12px;pointer-events:none;z-index:3000;max-width:250px;display:none}
.galaxy-tooltip.visible{display:block}
.galaxy-zoom-indicator{position:absolute;bottom:16px;left:16px;background:rgba(10,14,39,.85);border:1px solid rgba(201,169,97,.3);border-radius:8px;padding:6px 12px;color:#fff;font-size:12px;z-index:10;font-weight:600}
.galaxy-stats{font-size:12px;color:var(--text-muted);display:flex;gap:16px;flex-wrap:wrap}
.galaxy-stat-value{font-weight:700;color:var(--primary);background:rgba(26,54,93,.1);padding:2px 8px;border-radius:8px}
[data-theme="dark"] .galaxy-stat-value{color:var(--accent);background:rgba(201,169,97,.15)}
.dashboard-modal-body{padding:20px;background:var(--bg);overflow-y:auto}
.dashboard-container{display:flex;flex-direction:column;gap:20px}
.dashboard-kpi-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px}
.dashboard-kpi-card{background:var(--card-bg);border:1px solid var(--border);border-radius:var(--radius);padding:16px;text-align:center;position:relative;overflow:hidden}
.dashboard-kpi-card::before{content:'';position:absolute;top:0;right:0;left:0;height:3px;background:linear-gradient(90deg,var(--primary),var(--accent))}
.dashboard-kpi-icon{font-size:28px;margin-bottom:8px}
.dashboard-kpi-value{font-size:28px;font-weight:800;color:var(--primary);line-height:1;margin-bottom:4px}
[data-theme="dark"] .dashboard-kpi-value{color:var(--accent)}
.dashboard-kpi-label{font-size:11px;color:var(--text-muted);font-weight:600}
.dashboard-charts-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px}
.dashboard-chart-card{background:var(--card-bg);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}
.dashboard-chart-header{padding:12px 16px;border-bottom:1px solid var(--border-light);display:flex;align-items:center;gap:8px;font-size:13px;font-weight:700;color:var(--primary)}
[data-theme="dark"] .dashboard-chart-header{color:var(--accent)}
.dashboard-chart-body{padding:16px;display:flex;justify-content:center;align-items:center;min-height:220px}
.dashboard-chart-body svg{max-width:100%;height:auto}
.dashboard-empty-chart{display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text-muted);font-size:12px;gap:8px;text-align:center;padding:20px}
.dashboard-empty-chart span{font-size:32px;opacity:.4}
.dashboard-footer-stats{font-size:11px;color:var(--text-muted);display:flex;gap:16px;flex-wrap:wrap}
.dashboard-footer-stats .stat-highlight{font-weight:700;color:var(--primary)}
[data-theme="dark"] .dashboard-footer-stats .stat-highlight{color:var(--accent)}
.d3-tooltip{position:fixed;background:var(--primary-dark);color:#fff;padding:8px 12px;border-radius:6px;font-size:11px;font-weight:600;pointer-events:none;z-index:3000;display:none;font-family:'Vazirmatn',sans-serif}
.d3-tooltip.visible{display:block}
::-webkit-scrollbar{width:8px;height:8px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}
</style>
</head>
<body>
<header class="app-header"><div class="app-brand"><div class="app-logo">📊</div><div class="app-brand-text"><div class="app-title" id="headerTitle">مدیریت محصولات</div><div class="app-subtitle" id="headerSubtitle">مدیریت نسخه‌ها و نیازمندی‌های محصولات</div></div></div></div>
<div class="tabs-bar" id="advancedToolbar"><div class="toolbar-controls"><button class="toolbar-icon-btn" id="sidebarToggle" title="سایدبار"><span id="sidebarIcon">✕</span></button><button class="toolbar-icon-btn" id="themeToggle" title="تم"><span id="themeIcon">🌙</span></button><button class="toolbar-icon-btn" id="undoBtn" title="Ctrl+Z" disabled>↩</button><button class="toolbar-icon-btn" id="redoBtn" title="Ctrl+Y" disabled>↪</button></div><div class="toolbar-search"><span style="position:absolute;right:10px;opacity:.7">🔍</span><input type="text" class="search-input" id="toolbarSearch" placeholder="جستجو..."></div><div class="toolbar-filters"><div class="filter-dropdown"><span>🏷️</span><select class="filter-select" id="filterType"><option value="">همه انواع</option><option value="backend">بک‌اند</option><option value="frontend">فرانت‌اند</option><option value="service">میکروسرویس</option><option value="database">پایگاه داده</option><option value="mobile">موبایل</option><option value="infrastructure">زیرساخت</option></select></div><div class="filter-dropdown"><span>🌐</span><select class="filter-select" id="filterEnvironment"><option value="">همه محیط‌ها</option></select></div></div><div class="toolbar-stats"><div class="stat-item">📋 <span class="stat-value" id="statTotal">0</span></div><div class="stat-item">📦 <span class="stat-value" id="statProjects">0</span></div><div class="stat-item">🌿 <span class="stat-value" id="statSubProjects">0</span></div><div class="stat-item">🔗 <span class="stat-value" id="statDependencies">0</span></div></div></div>
<div class="app-main"><aside class="sidebar" id="sidebar"><div class="sidebar-header"><div class="sidebar-title">🛠️ ابزارها</div></div><div class="sidebar-body" id="sidebarBody"></div></aside><main class="board-area" id="boardArea"></main></div>
<footer class="app-footer" id="appFooter"><div><span class="footer-brand">طراح و توسعه‌دهنده:</span> هادی خزاعی اصل <span style="margin:0 10px">|</span> <span class="footer-brand">شرکت:</span> رایان پرداز کاوش</div><div><span class="footer-brand">نسخه:</span> 1.4.20</div></footer>
<!-- Settings Modal -->
<div class="modal-overlay" id="settingsModal"><div class="modal modal-lg"><div class="modal-header"><div class="modal-title"><span>⚙️</span><span>تنظیمات محصول</span></div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body"><div class="readonly-info">ℹ️ اطلاعات پایه محصول فقط قابل مشاهده هستند.</div><div class="form-row"><div class="form-group"><label class="form-label">عنوان محصول <span class="readonly-badge">فقط خواندنی</span></label><input type="text" class="form-input" id="settingProjectTitle" readonly></div><div class="form-group"><label class="form-label">نسخه <span class="readonly-badge">فقط خواندنی</span></label><input type="text" class="form-input" id="settingVersion" readonly></div></div><div class="form-group"><label class="form-label">توضیحات <span class="readonly-badge">فقط خواندنی</span></label><textarea class="form-textarea" id="settingDescription" readonly></textarea></div><div class="form-row"><div class="form-group"><label class="form-label">توسعه‌دهنده <span class="readonly-badge">فقط خواندنی</span></label><input type="text" class="form-input" id="settingDeveloper" readonly></div><div class="form-group"><label class="form-label">شرکت <span class="readonly-badge">فقط خواندنی</span></label><input type="text" class="form-input" id="settingCompany" readonly></div></div><div class="webhook-section"><div class="webhook-section-header"><div class="webhook-section-title">🔗 تنظیمات وب‌هوک</div><div class="webhook-toggle"><span>فعال‌سازی</span><label class="switch"><input type="checkbox" id="settingWebhookEnabled"><span class="slider"></span></label></div></div><div class="form-group"><label class="form-label">آدرس URL وب‌هوک <span class="required">*</span></label><input type="url" class="form-input" id="settingWebhookUrl" dir="ltr"></div><div class="form-group"><label class="form-label">شناسه کلاینت <span class="required">*</span></label><input type="text" class="form-input" id="settingWebhookClientId" dir="ltr"></div><div class="webhook-status"><span class="webhook-status-dot" id="webhookStatusDot"></span><span id="webhookStatusText">وضعیت: غیرفعال</span></div></div></div><div class="modal-footer"><button class="btn btn-accent" id="saveSettingsBtn">💾 ذخیره</button><button class="btn btn-ghost" data-close-modal>بستن</button></div></div></div>
<!-- Environment Modals -->
<div class="modal-overlay" id="envManagerModal"><div class="modal modal-lg"><div class="modal-header"><div class="modal-title">🌍 افزودن محیط عملیاتی جدید</div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body"><div class="form-row"><div class="form-group"><label class="form-label">نام محیط <span class="required">*</span></label><input type="text" class="form-input" id="newEnvName"></div><div class="form-group"><label class="form-label">توضیحات</label><input type="text" class="form-input" id="newEnvDesc"></div></div><div class="form-row"><div class="form-group"><label class="form-label">رنگ پس‌زمینه</label><div class="color-input-wrapper"><input type="color" id="newEnvBgColor" value="#38a169"><span class="color-preview-text" id="newEnvBgColorText">#38a169</span></div></div><div class="form-group"><label class="form-label">رنگ متن</label><div class="color-input-wrapper"><input type="color" id="newEnvTextColor" value="#ffffff"><span class="color-preview-text" id="newEnvTextColorText">#ffffff</span></div></div></div></div><div class="modal-footer"><button class="btn btn-accent" id="saveNewEnvBtn">💾 ثبت</button><button class="btn btn-ghost" data-close-modal>انصراف</button></div></div></div>
<div class="modal-overlay" id="envEditModal"><div class="modal"><div class="modal-header"><div class="modal-title">✏️ ویرایش محیط عملیاتی</div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body"><div class="form-group"><label class="form-label">شناسه <span class="readonly-badge">فقط خواندنی</span></label><input type="text" class="form-input" id="editEnvId" readonly></div><div class="form-group"><label class="form-label">نام <span class="required">*</span></label><input type="text" class="form-input" id="editEnvName"></div><div class="form-group"><label class="form-label">توضیحات</label><input type="text" class="form-input" id="editEnvDesc"></div><div class="form-row"><div class="form-group"><label class="form-label">رنگ پس‌زمینه</label><div class="color-input-wrapper"><input type="color" id="editEnvBgColor"><span class="color-preview-text" id="editEnvBgColorText"></span></div></div><div class="form-group"><label class="form-label">رنگ متن</label><div class="color-input-wrapper"><input type="color" id="editEnvTextColor"><span class="color-preview-text" id="editEnvTextColorText"></span></div></div></div><div class="form-group"><label class="form-label">پیش‌نمایش</label><div id="editEnvPreview" class="env-color-preview">نمونه</div></div></div><div class="modal-footer"><button class="btn btn-accent" id="saveEnvEditBtn">💾 ذخیره</button><button class="btn btn-ghost" data-close-modal>انصراف</button></div></div></div>
<!-- Product Detail Modal -->
<div class="modal-overlay" id="projectDetailModal"><div class="modal modal-xl"><div class="modal-header"><div class="modal-title" id="projectDetailTitle">📦 جزئیات محصول</div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body" id="projectDetailBody"></div><div class="modal-footer"><button class="btn btn-danger btn-sm" id="projectDetailDeleteBtn">🗑️ حذف محصول</button><button class="btn btn-ghost" data-close-modal>بستن</button></div></div></div>
<div class="modal-overlay" id="productEditModal"><div class="modal modal-lg"><div class="modal-header"><div class="modal-title">✏️ ویرایش محصول</div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body"><div class="form-group"><label class="form-label">شناسه <span class="readonly-badge">فقط خواندنی</span></label><input type="text" class="form-input" id="editProductId" readonly></div><div class="form-group"><label class="form-label">عنوان <span class="required">*</span></label><input type="text" class="form-input" id="editProductTitle"></div><div class="form-group"><label class="form-label">توضیحات</label><textarea class="form-textarea" id="editProductDescription"></textarea></div><div class="form-row"><div class="form-group"><label class="form-label">مالک</label><input type="text" class="form-input" id="editProductOwner"></div><div class="form-group"><label class="form-label">تاریخ ایجاد <span class="readonly-badge">فقط خواندنی</span></label><input type="text" class="form-input" id="editProductCreatedAt" readonly></div></div></div><div class="modal-footer"><button class="btn btn-accent" id="saveProductEditBtn">💾 ذخیره</button><button class="btn btn-ghost" data-close-modal>انصراف</button></div></div></div>
<div class="modal-overlay" id="addProductModal"><div class="modal modal-lg"><div class="modal-header"><div class="modal-title">➕ ایجاد محصول جدید</div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body"><div class="form-group"><label class="form-label">عنوان <span class="required">*</span></label><input type="text" class="form-input" id="newProductTitle"><div class="form-error" id="newProductTitleError">عنوان محصول الزامی است و باید حداقل ۳ کاراکتر باشد</div></div><div class="form-group"><label class="form-label">توضیحات</label><textarea class="form-textarea" id="newProductDescription"></textarea></div><div class="form-row"><div class="form-group"><label class="form-label">مالک</label><input type="text" class="form-input" id="newProductOwner"></div><div class="form-group"><label class="form-label">تاریخ ایجاد <span class="readonly-badge">فقط خواندنی</span></label><input type="text" class="form-input" id="newProductCreatedAt" readonly></div></div></div><div class="modal-footer"><button class="btn btn-accent" id="saveNewProductBtn">💾 ثبت</button><button class="btn btn-ghost" data-close-modal>انصراف</button></div></div></div>
<div class="modal-overlay" id="addProjectModal"><div class="modal modal-lg"><div class="modal-header"><div class="modal-title">➕ افزودن پروژه جدید</div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body"><div class="form-group"><label class="form-label">عنوان <span class="required">*</span></label><input type="text" class="form-input" id="newProjectTitle"></div><div class="form-row"><div class="form-group"><label class="form-label">نوع <span class="required">*</span></label><select class="form-select" id="newProjectType"><option value="">انتخاب کنید...</option></select></div><div class="form-group"><label class="form-label">فریم‌ورک</label><input type="text" class="form-input" id="newProjectFramework"></div></div><div class="form-row"><div class="form-group"><label class="form-label">زبان</label><input type="text" class="form-input" id="newProjectLanguage"></div><div class="form-group"><label class="form-label">آدرس مخزن</label><input type="text" class="form-input" id="newProjectRepo" dir="ltr"></div></div><div class="form-group"><label class="form-label">توضیحات</label><textarea class="form-textarea" id="newProjectDescription"></textarea></div></div><div class="modal-footer"><button class="btn btn-accent" id="saveNewProjectBtn">💾 ذخیره</button><button class="btn btn-ghost" data-close-modal>انصراف</button></div></div></div>
<div class="modal-overlay" id="editProjectModal"><div class="modal modal-lg"><div class="modal-header"><div class="modal-title">✏️ ویرایش پروژه</div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body"><div class="form-group"><label class="form-label">شناسه <span class="readonly-badge">فقط خواندنی</span></label><input type="text" class="form-input" id="editProjId" readonly></div><div class="form-group"><label class="form-label">عنوان <span class="required">*</span></label><input type="text" class="form-input" id="editProjTitle"></div><div class="form-row"><div class="form-group"><label class="form-label">نوع <span class="required">*</span></label><select class="form-select" id="editProjType"><option value="">انتخاب کنید...</option></select></div><div class="form-group"><label class="form-label">فریم‌ورک</label><input type="text" class="form-input" id="editProjFramework"></div></div><div class="form-row"><div class="form-group"><label class="form-label">زبان</label><input type="text" class="form-input" id="editProjLanguage"></div><div class="form-group"><label class="form-label">آدرس مخزن</label><input type="text" class="form-input" id="editProjRepo" dir="ltr"></div></div><div class="form-group"><label class="form-label">توضیحات</label><textarea class="form-textarea" id="editProjDescription"></textarea></div></div><div class="modal-footer"><button class="btn btn-accent" id="saveProjEditBtn">💾 ذخیره</button><button class="btn btn-ghost" data-close-modal>انصراف</button></div></div></div>
<!-- Deployment Modal -->
<div class="modal-overlay" id="deploymentModal"><div class="modal modal-lg"><div class="modal-header"><div class="modal-title" id="deploymentModalTitle">🚀 افزودن نسخه استقرار</div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body"><div class="form-group" id="deploymentEnvGroup"></div><div class="form-row"><div class="form-group"><label class="form-label">نسخه <span class="required">*</span></label><input type="text" class="form-input" id="depVersion" dir="ltr" placeholder="مثلاً: 1.2.3"><div class="form-hint">از فرمت SemVer استفاده کنید</div></div><div class="form-group"><label class="form-label">تاریخ و زمان استقرار <span class="required">*</span></label><input type="datetime-local" class="form-input" id="depDate" dir="ltr"></div></div><div class="form-group"><label class="form-label">ریلیز نوت <span class="required">*</span></label><textarea class="form-textarea" id="depReleaseNotes" rows="3" placeholder="توضیحات تغییرات این نسخه..."></textarea><div class="form-hint">شرح تغییرات، باگ‌فیکس‌ها و ویژگی‌های جدید این نسخه</div></div><div id="requiredDeploymentsSection"></div></div><div class="modal-footer"><button class="btn btn-accent" id="saveDeploymentBtn">💾 ذخیره</button><button class="btn btn-ghost" data-close-modal>انصراف</button></div></div></div>
<!-- Dependency Manager Modal -->
<div class="modal-overlay" id="dependencyManagerModal"><div class="modal modal-lg"><div class="modal-header"><div class="modal-title">🔗 مدیریت وابستگی‌ها</div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body"><div class="readonly-info">ℹ️ هر وابستگی فقط شامل یک پروژه است. انتخاب نسخه و محیط در زمان ثبت استقرار انجام می‌شود.</div><div class="dependency-section-title">📋 وابستگی‌های فعلی</div><div id="currentDependenciesList" class="dependency-list"></div><div class="dependency-search-box"><div class="dependency-section-title">🔍 افزودن وابستگی جدید</div><input type="text" class="dependency-search-input" id="dependencySearch" placeholder="جستجو در پروژه‌ها..."><div id="dependencySearchResults" class="dependency-search-results"></div></div></div><div class="modal-footer"><button class="btn btn-ghost" data-close-modal>بستن</button></div></div></div>
<!-- Galaxy Chart Modal -->
<div class="modal-overlay" id="galaxyChartModal"><div class="modal modal-galaxy"><div class="modal-header"><div class="modal-title">🌌 نمودار کهکشانی محصولات</div><div class="galaxy-toolbar"><button class="btn btn-sm" id="galaxyResetView">🎯 بازنشانی</button><button class="btn btn-sm" id="galaxyExportPNG">📷 PNG</button></div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body galaxy-modal-body"><div id="galaxyChartContainer" class="galaxy-chart-container"></div><div class="galaxy-legend" id="galaxyLegend"></div><div class="galaxy-info-panel" id="galaxyInfoPanel"><button class="modal-close" id="galaxyInfoClose" style="position:absolute;top:8px;left:8px;width:22px;height:22px;font-size:12px">✕</button><div id="galaxyInfoContent"></div></div><div class="galaxy-zoom-indicator" id="galaxyZoomIndicator">🔍 100%</div></div><div class="modal-footer"><div class="galaxy-stats" id="galaxyStats"></div><button class="btn btn-ghost" data-close-modal>بستن</button></div></div></div>
<!-- Executive Dashboard Modal -->
<div class="modal-overlay" id="executiveDashboardModal"><div class="modal modal-galaxy"><div class="modal-header"><div class="modal-title">📊 داشبورد مدیریتی</div><div class="galaxy-toolbar"><button class="btn btn-sm" id="dashboardRefresh">🔄 به‌روزرسانی</button></div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body dashboard-modal-body"><div id="dashboardContainer" class="dashboard-container"></div></div><div class="modal-footer"><div class="dashboard-footer-stats" id="dashboardFooterStats"></div><button class="btn btn-ghost" data-close-modal>بستن</button></div></div></div>
<!-- Dependency Analysis Modal -->
<div class="modal-overlay" id="dependencyAnalysisModal"><div class="modal modal-galaxy"><div class="modal-header"><div class="modal-title">🔗 تحلیل وابستگی‌ها</div><div class="galaxy-toolbar"><button class="btn btn-sm" id="depAnalysisRefresh">🔄</button></div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body dashboard-modal-body"><div id="dependencyAnalysisContainer" class="dashboard-container"></div></div><div class="modal-footer"><div class="dashboard-footer-stats" id="depAnalysisFooterStats"></div><button class="btn btn-ghost" data-close-modal>بستن</button></div></div></div>
<!-- Deployment Analysis Modal -->
<div class="modal-overlay" id="deploymentAnalysisModal"><div class="modal modal-galaxy"><div class="modal-header"><div class="modal-title">🚀 تحلیل استقرار</div><div class="galaxy-toolbar"><button class="btn btn-sm" id="deployAnalysisRefresh">🔄</button></div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body dashboard-modal-body"><div id="deploymentAnalysisContainer" class="dashboard-container"></div></div><div class="modal-footer"><div class="dashboard-footer-stats" id="deployAnalysisFooterStats"></div><button class="btn btn-ghost" data-close-modal>بستن</button></div></div></div>
<!-- Version Analysis Modal -->
<div class="modal-overlay" id="versionAnalysisModal"><div class="modal modal-galaxy"><div class="modal-header"><div class="modal-title">🔢 تحلیل نسخه</div><div class="galaxy-toolbar"><button class="btn btn-sm" id="verAnalysisRefresh">🔄</button></div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body dashboard-modal-body"><div id="versionAnalysisContainer" class="dashboard-container"></div></div><div class="modal-footer"><div class="dashboard-footer-stats" id="verAnalysisFooterStats"></div><button class="btn btn-ghost" data-close-modal>بستن</button></div></div></div>
<!-- Risk Analysis Modal -->
<div class="modal-overlay" id="riskAnalysisModal"><div class="modal modal-galaxy"><div class="modal-header"><div class="modal-title">⚠️ تحلیل ریسک</div><div class="galaxy-toolbar"><button class="btn btn-sm" id="riskAnalysisRefresh">🔄</button></div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body dashboard-modal-body"><div id="riskAnalysisContainer" class="dashboard-container"></div></div><div class="modal-footer"><div class="dashboard-footer-stats" id="riskAnalysisFooterStats"></div><button class="btn btn-ghost" data-close-modal>بستن</button></div></div></div>
<div class="toast-container" id="toastContainer"></div>
<div class="galaxy-tooltip" id="galaxyTooltip"></div>
<div class="d3-tooltip" id="d3Tooltip"></div>
<script>
/* ==================== DATA ==================== */
const projectData={projectId:'550e8400-e29b-41d4-a716-446655440000',projectName:'xReleaseAssistant',projectTitle:'دستیار انتشار',description:'مدیریت نسخه‌ها و نیازمندی‌های محصولات',version:'1.1.0',developer:'هادی خزاعی اصل',company:'رایان پرداز کاوش',metadata:{createdAt:'2026-09-13T00:00:00Z',updatedAt:'2026-09-14T00:00:00Z',tags:['release-management'],customFields:{}},webhook:{url:'',clientId:'',enabled:false},toolGroups:[{id:'product-management',title:'پرونده',icon:'📁',description:'مدیریت چرخه حیات محصول',tools:[{id:'clear-all',title:'پاکسازی',icon:'🗑️',action:'clearAll',description:'پاکسازی کامل'},{id:'save-product',title:'ذخیره',icon:'💾',action:'saveProduct',description:'دانلود داده‌ها'},{id:'load-product',title:'بارگذاری',icon:'📂',action:'loadProduct',description:'بارگذاری از فایل'},{id:'product-settings',title:'تنظیمات',icon:'⚙️',action:'openProductSettings',description:'پیکربندی'}]},{id:'reports',title:'گزارش‌ها',icon:'📊',description:'تحلیل و گزارش‌گیری',tools:[{id:'executive-dashboard',title:'داشبورد مدیریتی',icon:'📊',action:'showExecutiveDashboard',description:'نمای کلی'},{id:'dependency-analysis',title:'تحلیل وابستگی‌ها',icon:'🔗',action:'showDependencyAnalysis',description:'تحلیل وابستگی‌ها'},{id:'deployment-analysis',title:'تحلیل استقرار',icon:'🚀',action:'showDeploymentAnalysis',description:'تحلیل استقرارها'},{id:'version-analysis',title:'تحلیل نسخه',icon:'🔢',action:'showVersionAnalysis',description:'تحلیل نسخه‌ها'},{id:'risk-analysis',title:'تحلیل ریسک',icon:'⚠️',action:'showRiskAnalysis',description:'تحلیل ریسک‌ها'},{id:'galaxy-chart',title:'نمودار کهکشانی',icon:'🌌',action:'showGalaxyChart',description:'نمودار کهکشانی'}]}],
appData:{environments:[{id:'env-prod-001',name:'Production',description:'محیط عملیاتی اصلی',backgroundColor:'#38a169',textColor:'#ffffff'},{id:'env-stage-002',name:'Staging',description:'محیط پیش از تولید',backgroundColor:'#dd6b20',textColor:'#ffffff'},{id:'env-dev-003',name:'Development',description:'محیط توسعه',backgroundColor:'#3182ce',textColor:'#ffffff'},{id:'env-qa-004',name:'QA',description:'تست کیفیت',backgroundColor:'#805ad5',textColor:'#ffffff'},{id:'env-uat-005',name:'UAT',description:'تست پذیرش کاربر',backgroundColor:'#d69e2e',textColor:'#ffffff'},{id:'env-dr-006',name:'Disaster Recovery',description:'بازیابی پس از بحران',backgroundColor:'#e53e3e',textColor:'#ffffff'}],
products:[
{id:'prod-ecommerce',title:'سامانه فروشگاه آنلاین دیجی‌مارکت',description:'پلتفرم جامع تجارت الکترونیک',metadata:{createdAt:'2026-01-15T08:00:00Z',owner:'تیم توسعه تجارت الکترونیک',customFields:{industry:'E-commerce',priority:'critical'}},projects:[{id:'proj-ecom-api',title:'E-commerce Core API',description:'سرویس بک‌اند اصلی فروشگاه',type:'backend',repositoryUrl:'https://git.rayanpardaz.ir/ecommerce/core-api',metadata:{framework:'.NET 8',language:'C#'},dependencies:['proj-payment-gateway','proj-inventory-svc'],deployments:[{id:'dep-ecom-api-prod',environmentId:'env-prod-001',version:'3.5.2',deploymentDate:'2026-09-10T14:30:00Z',releaseNotes:'رفع باگ پردازش سفارش و بهبود عملکرد',requiredDeployments:[{projectId:'proj-payment-gateway',deploymentId:'dep-payment-prod'},{projectId:'proj-inventory-svc',deploymentId:'dep-inventory-prod'}],status:'active'},{id:'dep-ecom-api-stage',environmentId:'env-stage-002',version:'3.6.0-rc.1',deploymentDate:'2026-09-14T09:00:00Z',releaseNotes:'افزودن قابلیت چندزبانه',requiredDeployments:[],status:'active'},{id:'dep-ecom-api-dev',environmentId:'env-dev-003',version:'3.7.0-dev.12',deploymentDate:'2026-09-15T16:00:00Z',releaseNotes:'پیاده‌سازی اولیه ماژول تخفیف',requiredDeployments:[],status:'active'}]},{id:'proj-ecom-web',title:'E-commerce Web Frontend',description:'اپلیکیشن وب فروشگاه',type:'frontend',repositoryUrl:'https://git.rayanpardaz.ir/ecommerce/web-app',metadata:{framework:'Angular 18',language:'TypeScript'},dependencies:['proj-ecom-api'],deployments:[{id:'dep-ecom-web-prod',environmentId:'env-prod-001',version:'4.2.1',deploymentDate:'2026-09-11T10:00:00Z',releaseNotes:'بهبود UX صفحه پرداخت',requiredDeployments:[{projectId:'proj-ecom-api',deploymentId:'dep-ecom-api-prod'}],status:'active'},{id:'dep-ecom-web-stage',environmentId:'env-stage-002',version:'4.3.0-rc.1',deploymentDate:'2026-09-13T11:30:00Z',releaseNotes:'پیاده‌سازی Dark Mode',requiredDeployments:[],status:'active'}]},{id:'proj-ecom-mobile',title:'E-commerce Mobile App',description:'اپلیکیشن موبایل فروشگاه',type:'mobile',repositoryUrl:'https://git.rayanpardaz.ir/ecommerce/mobile-app',metadata:{framework:'Flutter 3.22',language:'Dart'},dependencies:['proj-ecom-api','proj-notification-svc'],deployments:[{id:'dep-ecom-mobile-prod',environmentId:'env-prod-001',version:'2.8.0',deploymentDate:'2026-09-08T08:00:00Z',releaseNotes:'انتشار در استورها',requiredDeployments:[{projectId:'proj-ecom-api',deploymentId:'dep-ecom-api-prod'}],status:'active'}]},{id:'proj-ecom-db',title:'E-commerce Database',description:'پایگاه داده اصلی',type:'database',repositoryUrl:'https://git.rayanpardaz.ir/ecommerce/db-migrations',metadata:{framework:'SQL Server 2022',language:'T-SQL'},dependencies:[],deployments:[{id:'dep-ecom-db-prod',environmentId:'env-prod-001',version:'5.1.0',deploymentDate:'2026-09-05T02:00:00Z',releaseNotes:'افزودن ایندکس‌های جدید',requiredDeployments:[],status:'active'},{id:'dep-ecom-db-dr',environmentId:'env-dr-006',version:'5.1.0',deploymentDate:'2026-09-05T03:00:00Z',releaseNotes:'همگام‌سازی با Production',requiredDeployments:[],status:'active'}]},{id:'proj-ecom-search',title:'E-commerce Search Service',description:'سرویس جستجوی پیشرفته',type:'service',repositoryUrl:'https://git.rayanpardaz.ir/ecommerce/search-svc',metadata:{framework:'.NET 8',language:'C#'},dependencies:['proj-ecom-db'],deployments:[{id:'dep-ecom-search-prod',environmentId:'env-prod-001',version:'1.9.3',deploymentDate:'2026-09-09T13:00:00Z',releaseNotes:'بهبود الگوریتم جستجو',requiredDeployments:[{projectId:'proj-ecom-db',deploymentId:'dep-ecom-db-prod'}],status:'active'}]},{id:'proj-ecom-infra',title:'E-commerce Infrastructure',description:'زیرساخت Kubernetes',type:'infrastructure',repositoryUrl:'https://git.rayanpardaz.ir/ecommerce/infra',metadata:{framework:'Kubernetes 1.29',language:'YAML/Helm'},dependencies:[],deployments:[{id:'dep-ecom-infra-prod',environmentId:'env-prod-001',version:'2.1.0',deploymentDate:'2026-09-01T06:00:00Z',releaseNotes:'به‌روزرسانی Helm Charts',requiredDeployments:[],status:'active'}]}]},
{id:'prod-payment',title:'پلتفرم پرداخت و کیف پول پرداخت‌یار',description:'زیرساخت مشترک پرداخت',metadata:{createdAt:'2026-01-05T08:00:00Z',owner:'تیم توسعه Payment',customFields:{industry:'FinTech',priority:'critical',shared:true}},projects:[{id:'proj-payment-gateway',title:'Payment Gateway Service',description:'درگاه پرداخت یکپارچه',type:'service',repositoryUrl:'https://git.rayanpardaz.ir/payment/gateway',metadata:{framework:'.NET 8',language:'C#'},dependencies:['proj-payment-db'],deployments:[{id:'dep-payment-prod',environmentId:'env-prod-001',version:'5.3.0',deploymentDate:'2026-09-09T12:00:00Z',releaseNotes:'پشتیبانی از درگاه‌های جدید',requiredDeployments:[{projectId:'proj-payment-db',deploymentId:'dep-payment-db-prod'}],status:'active'},{id:'dep-payment-stage',environmentId:'env-stage-002',version:'5.4.0-rc.1',deploymentDate:'2026-09-14T10:00:00Z',releaseNotes:'تست یکپارچگی با شاپرک',requiredDeployments:[],status:'active'},{id:'dep-payment-qa',environmentId:'env-qa-004',version:'5.4.0-beta.2',deploymentDate:'2026-09-15T15:00:00Z',releaseNotes:'تست‌های خودکار امنیتی',requiredDeployments:[],status:'active'}]},{id:'proj-payment-wallet',title:'Digital Wallet Service',description:'سرویس کیف پول الکترونیک',type:'service',repositoryUrl:'https://git.rayanpardaz.ir/payment/wallet',metadata:{framework:'.NET 8',language:'C#'},dependencies:['proj-payment-gateway','proj-payment-db'],deployments:[{id:'dep-payment-wallet-prod',environmentId:'env-prod-001',version:'3.1.2',deploymentDate:'2026-09-11T11:00:00Z',releaseNotes:'افزودن قابلیت انتقال بین کیف پول',requiredDeployments:[{projectId:'proj-payment-gateway',deploymentId:'dep-payment-prod'}],status:'active'}]},{id:'proj-payment-db',title:'Payment Database',description:'پایگاه داده تراکنش‌های مالی',type:'database',repositoryUrl:'https://git.rayanpardaz.ir/payment/db',metadata:{framework:'Oracle 19c RAC',language:'PL/SQL'},dependencies:[],deployments:[{id:'dep-payment-db-prod',environmentId:'env-prod-001',version:'6.0.0',deploymentDate:'2026-09-01T02:00:00Z',releaseNotes:'ارتقای ساختار تراکنش‌ها',requiredDeployments:[],status:'active'},{id:'dep-payment-db-dr',environmentId:'env-dr-006',version:'6.0.0',deploymentDate:'2026-09-01T03:00:00Z',releaseNotes:'همگام‌سازی با Production',requiredDeployments:[],status:'active'}]},{id:'proj-payment-web',title:'Payment Management Portal',description:'پرتال مدیریت تراکنش‌ها',type:'frontend',repositoryUrl:'https://git.rayanpardaz.ir/payment/portal',metadata:{framework:'Angular 18',language:'TypeScript'},dependencies:['proj-payment-gateway'],deployments:[{id:'dep-payment-web-prod',environmentId:'env-prod-001',version:'2.8.1',deploymentDate:'2026-09-12T10:00:00Z',releaseNotes:'داشبورد تحلیل تراکنش‌ها',requiredDeployments:[{projectId:'proj-payment-gateway',deploymentId:'dep-payment-prod'}],status:'active'}]},{id:'proj-auth-svc',title:'Authentication & Authorization Service',description:'سرویس مشترک احراز هویت',type:'service',repositoryUrl:'https://git.rayanpardaz.ir/shared/auth-svc',metadata:{framework:'.NET 8',language:'C#'},dependencies:['proj-payment-db'],deployments:[{id:'dep-auth-prod',environmentId:'env-prod-001',version:'4.1.0',deploymentDate:'2026-09-08T06:00:00Z',releaseNotes:'افزودن MFA',requiredDeployments:[],status:'active'},{id:'dep-auth-stage',environmentId:'env-stage-002',version:'4.2.0-rc.1',deploymentDate:'2026-09-14T08:00:00Z',releaseNotes:'تست SSO با Azure AD',requiredDeployments:[],status:'active'}]},{id:'proj-notification-svc',title:'Notification Service',description:'سرویس مشترک ارسال اعلان',type:'service',repositoryUrl:'https://git.rayanpardaz.ir/shared/notification-svc',metadata:{framework:'.NET 8',language:'C#'},dependencies:[],deployments:[{id:'dep-notif-prod',environmentId:'env-prod-001',version:'2.5.0',deploymentDate:'2026-09-07T07:00:00Z',releaseNotes:'افزودن کانال WhatsApp',requiredDeployments:[],status:'active'}]},{id:'proj-inventory-svc',title:'Inventory Management Service',description:'سرویس مدیریت موجودی انبار',type:'service',repositoryUrl:'https://git.rayanpardaz.ir/shared/inventory-svc',metadata:{framework:'.NET 8',language:'C#'},dependencies:['proj-payment-db'],deployments:[{id:'dep-inventory-prod',environmentId:'env-prod-001',version:'2.0.3',deploymentDate:'2026-09-06T10:00:00Z',releaseNotes:'همگام‌سازی Real-time',requiredDeployments:[],status:'active'}]}]}
]}};
const VALID_PROJECT_TYPES=[{value:'backend',label:'بک‌اند (Backend)',icon:'🖥️'},{value:'frontend',label:'فرانت‌اند (Frontend)',icon:'🌐'},{value:'service',label:'میکروسرویس (Service)',icon:'⚙️'},{value:'database',label:'پایگاه داده (Database)',icon:'🗄️'},{value:'mobile',label:'موبایل (Mobile)',icon:'📱'},{value:'infrastructure',label:'زیرساخت (Infrastructure)',icon:'🏗️'}];
function validateAppDataStructure(d){return d&&typeof d==='object'&&Array.isArray(d.environments)&&Array.isArray(d.products)}
function populateProjectTypeDropdowns(){['newProjectType','editProjType'].forEach(id=>{const s=document.getElementById(id);if(!s)return;const cv=s.value;s.innerHTML='<option value="">انتخاب کنید...</option>'+VALID_PROJECT_TYPES.map(t=>`<option value="${t.value}">${t.icon} ${t.label}</option>`).join('');if(cv)s.value=cv})}
function showToast(msg,type='info',dur=3000){const c=document.getElementById('toastContainer');const t=document.createElement('div');t.className=`toast ${type}`;const ic={success:'✅',error:'❌',warning:'⚠️',info:'ℹ️'};t.innerHTML=`<span>${ic[type]||'ℹ️'}</span><span>${msg}</span>`;c.appendChild(t);setTimeout(()=>{t.style.opacity='0';setTimeout(()=>t.remove(),300)},dur)}
function showD3Tooltip(e,text){let tt=document.getElementById('d3Tooltip');if(!tt)return;tt.textContent=text;tt.classList.add('visible');tt.style.left=(e.clientX+12)+'px';tt.style.top=(e.clientY+12)+'px'}
function hideD3Tooltip(){const tt=document.getElementById('d3Tooltip');if(tt)tt.classList.remove('visible')}
function refreshUI(){ToolbarManager.updateStats();BoardRenderer.render()}
/* ==================== MODAL MANAGER ==================== */
const ModalManager={stack:[],open(id){const m=document.getElementById(id);if(!m)return;this.stack=this.stack.filter(x=>x!==id);this.stack.push(id);this.stack.forEach((mid,i)=>{const el=document.getElementById(mid);if(!el)return;if(mid===id)el.classList.add('active');el.style.zIndex=1000+i+1})},close(id){const m=document.getElementById(id);if(!m)return;m.classList.remove('active');m.style.zIndex='';this.stack=this.stack.filter(x=>x!==id);this.stack.forEach((mid,i)=>{const el=document.getElementById(mid);if(el)el.style.zIndex=1000+i+1})},closeTop(){if(this.stack.length>0)this.close(this.stack[this.stack.length-1])},init(){document.querySelectorAll('[data-close-modal]').forEach(btn=>{btn.addEventListener('click',e=>{e.stopPropagation();const m=e.target.closest('.modal-overlay');if(m&&m.id)this.close(m.id)})});document.querySelectorAll('.modal-overlay').forEach(ov=>{ov.addEventListener('click',e=>{if(e.target===ov&&ov.id)this.close(ov.id)})});document.addEventListener('keydown',e=>{if(e.key==='Escape'){e.preventDefault();this.closeTop()}})}};
/* ==================== TOOLBAR MANAGER ==================== */
const ToolbarManager={currentFilters:{query:'',typeFilter:'',envFilter:''},init(){this.populateEnvironmentFilter();this.bindEvents();this.updateStats()},populateEnvironmentFilter(){const s=document.getElementById('filterEnvironment');if(!s)return;s.innerHTML='<option value="">همه محیط‌ها</option>'+projectData.appData.environments.map(e=>`<option value="${e.id}">${e.name}</option>`).join('')},bindEvents(){const si=document.getElementById('toolbarSearch'),ft=document.getElementById('filterType'),fe=document.getElementById('filterEnvironment');if(si)si.addEventListener('input',()=>{this.applyFilters();refreshUI()});if(ft)ft.addEventListener('change',()=>{this.applyFilters();refreshUI()});if(fe)fe.addEventListener('change',()=>{this.applyFilters();refreshUI()})},applyFilters(){this.currentFilters={query:(document.getElementById('toolbarSearch')?.value||'').trim().toLowerCase(),typeFilter:document.getElementById('filterType')?.value||'',envFilter:document.getElementById('filterEnvironment')?.value||''};this.updateStats()},updateStats(){const ps=projectData.appData.products||[];let tp=0,td=0;ps.forEach(p=>{(p.projects||[]).forEach(pr=>{tp++;td+=(pr.dependencies||[]).length})});const s=(id,v)=>{const el=document.getElementById(id);if(el)el.textContent=v};s('statTotal',ps.length+tp);s('statProjects',ps.length);s('statSubProjects',tp);s('statDependencies',td)},shouldShowProduct(product){const f=this.currentFilters;if(!f.query&&!f.typeFilter&&!f.envFilter)return true;return(product.projects||[]).some(pr=>{const mq=!f.query||pr.title.toLowerCase().includes(f.query)||product.title.toLowerCase().includes(f.query);const mt=!f.typeFilter||pr.type===f.typeFilter;const me=!f.envFilter||(pr.deployments||[]).filter(d=>d.status==='active').some(d=>d.environmentId===f.envFilter);return mq&&mt&&me})}};
/* ==================== HISTORY MANAGER ==================== */
const HistoryManager={undoStack:[],redoStack:[],maxSize:50,isUndoRedoInProgress:false,pushState(){if(this.isUndoRedoInProgress)return;this.undoStack.push(JSON.parse(JSON.stringify(projectData.appData)));if(this.undoStack.length>this.maxSize)this.undoStack.shift();this.redoStack=[];this.updateButtons()},undo(){if(this.undoStack.length===0){showToast('هیچ تغییری نیست','warning');return}this.redoStack.push(JSON.parse(JSON.stringify(projectData.appData)));this.isUndoRedoInProgress=true;projectData.appData=this.undoStack.pop();this.isUndoRedoInProgress=false;this.updateUI();this.updateButtons();showToast('↩️ بازگشت','success')},redo(){if(this.redoStack.length===0){showToast('هیچ تغییری نیست','warning');return}this.undoStack.push(JSON.parse(JSON.stringify(projectData.appData)));this.isUndoRedoInProgress=true;projectData.appData=this.redoStack.pop();this.isUndoRedoInProgress=false;this.updateUI();this.updateButtons();showToast('↪️ باز انجام','success')},updateUI(){refreshUI();if(ProductManager.currentDetailProductId)ProductManager.showDetail(ProductManager.currentDetailProductId)},updateButtons(){const u=document.getElementById('undoBtn'),r=document.getElementById('redoBtn');if(u)u.disabled=this.undoStack.length===0;if(r)r.disabled=this.redoStack.length===0},init(){this.pushState();document.getElementById('undoBtn').addEventListener('click',()=>this.undo());document.getElementById('redoBtn').addEventListener('click',()=>this.redo());document.addEventListener('keydown',e=>{if((e.ctrlKey||e.metaKey)&&e.key==='z'&&!e.shiftKey){e.preventDefault();this.undo()}if((e.ctrlKey||e.metaKey)&&(e.key==='y'||(e.key==='z'&&e.shiftKey))){e.preventDefault();this.redo()}})}};
/* ==================== SECTION COLLAPSE ==================== */
const SectionCollapseManager={storageKey:'boardSectionCollapseState',getState(){try{const s=localStorage.getItem(this.storageKey);return s?JSON.parse(s):{dashboard:false,products:false,environments:false}}catch{return{dashboard:false,products:false,environments:false}}},saveState(s){try{localStorage.setItem(this.storageKey,JSON.stringify(s))}catch(e){}},toggle(id){const s=this.getState();s[id]=!s[id];this.saveState(s);const sec=document.getElementById(`section-${id}`);if(sec)sec.classList.toggle('collapsed',s[id]);return s[id]},init(){const s=this.getState();Object.keys(s).forEach(id=>{const sec=document.getElementById(`section-${id}`);if(sec&&s[id])sec.classList.add('collapsed')});document.getElementById('boardArea').addEventListener('click',e=>{const ht=e.target.closest('.board-header-title');if(ht){const sec=ht.closest('.board-section');if(sec){this.toggle(sec.id.replace('section-',''))}}})}};
/* ==================== DASHBOARD SECTION ==================== */
const DashboardSectionRenderer={getSectionHTML(){const d=this.getData();let h=`<div class="board-section" id="section-dashboard"><div class="board-header"><div class="board-header-title"><span class="collapse-icon">▼</span>📊 داشبورد مدیریتی</div><div class="board-header-actions"><button class="btn btn-accent btn-sm" onclick="DashboardManager.open()">📊 مشاهده کامل</button></div></div><div class="section-content">`;h+='<div class="dashboard-kpi-row" style="margin-bottom:16px">';h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">📦</div><div class="dashboard-kpi-value">${d.products}</div><div class="dashboard-kpi-label">محصول</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🌿</div><div class="dashboard-kpi-value">${d.projects}</div><div class="dashboard-kpi-label">پروژه</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🔗</div><div class="dashboard-kpi-value">${d.deps}</div><div class="dashboard-kpi-label">وابستگی</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🚀</div><div class="dashboard-kpi-value">${d.deploys}</div><div class="dashboard-kpi-label">استقرار</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🌍</div><div class="dashboard-kpi-value">${d.environments}</div><div class="dashboard-kpi-label">محیط</div></div>`;h+='</div></div></div>';return h},getData(){const ps=projectData.appData.products||[];let tp=0,td=0,tdp=0;ps.forEach(prod=>{(prod.projects||[]).forEach(pr=>{tp++;td+=(pr.dependencies||[]).length;tdp+=(pr.deployments||[]).filter(d=>d.status==='active').length})});return{products:ps.length,projects:tp,deps:td,deploys:tdp,environments:projectData.appData.environments.length}}};
/* ==================== ENVIRONMENT RENDERER ==================== */
const EnvironmentRenderer={getSectionHTML(){const envs=projectData.appData.environments||[];let h=`<div class="board-section" id="section-environments"><div class="board-header"><div class="board-header-title"><span class="collapse-icon">▼</span>🌍 محیط‌های عملیاتی (${envs.length})</div><div class="board-header-actions"><button class="btn btn-accent" onclick="EnvironmentManager.openAddModal()">➕ محیط جدید</button></div></div><div class="section-content">`;if(envs.length===0){h+='<div class="board-empty"><div class="board-empty-icon">🌍</div><h2>هیچ محیطی نیست</h2></div>'}else{h+='<div class="env-card-grid">';envs.forEach(env=>{h+=`<div class="env-card-item"><div class="env-card-preview" style="background-color:${env.backgroundColor||'#38a169'};color:${env.textColor||'#fff'}">${env.name}</div><div class="env-card-body"><div class="env-card-name">${env.name}</div><div class="env-card-desc">${env.description||''}</div></div><div class="env-card-footer"><button class="btn btn-ghost btn-sm" onclick="EnvironmentManager.openEditModal('${env.id}')">✏️ ویرایش</button><button class="btn btn-danger btn-sm" onclick="EnvironmentManager.remove('${env.id}')">🗑️ حذف</button></div></div>`});h+='</div>'}h+='</div></div>';return h}};
/* ==================== BOARD RENDERER ==================== */
const BoardRenderer={render(){const board=document.getElementById('boardArea');const products=projectData.appData.products||[];const fp=products.filter(p=>ToolbarManager.shouldShowProduct(p));let h='';h+=DashboardSectionRenderer.getSectionHTML();h+=`<div class="board-section" id="section-products"><div class="board-header"><div class="board-header-title"><span class="collapse-icon">▼</span>📦 محصولات (${fp.length} از ${products.length})</div><div class="board-header-actions"><button class="btn btn-accent" onclick="ProductManager.openAddProductModal()">➕ محصول جدید</button></div></div><div class="section-content">`;if(products.length===0){h+='<div class="board-empty"><div class="board-empty-icon">📦</div><h2>هیچ محصولی نیست</h2></div>'}else if(fp.length===0){h+='<div class="no-results">🔍 نتیجه‌ای یافت نشد</div>'}else{h+='<div class="projects-grid">';fp.forEach(prod=>{const projects=prod.projects||[];const tc={};projects.forEach(pr=>{tc[pr.type]=(tc[pr.type]||0)+1});const td=projects.reduce((s,pr)=>s+(pr.dependencies||[]).length,0);const tdp=projects.reduce((s,pr)=>s+(pr.deployments||[]).filter(d=>d.status==='active').length,0);h+=`<div class="project-card"><div class="project-card-header"><div class="project-card-title-row"><span style="font-size:22px">📦</span><div class="project-card-title">${prod.title}</div></div><div class="project-card-desc">${prod.description||''}</div></div><div class="project-card-body"><div class="project-stat-row"><span>🌿 پروژه‌ها:</span><span class="project-stat-value">${projects.length}</span></div><div class="project-stat-row"><span>🔗 وابستگی‌ها:</span><span class="project-stat-value">${td}</span></div><div class="project-stat-row"><span>🚀 استقرارها:</span><span class="project-stat-value">${tdp}</span></div>${Object.keys(tc).length>0?`<div class="project-type-badges">${Object.entries(tc).map(([type,count])=>{const ti=VALID_PROJECT_TYPES.find(t=>t.value===type)||{label:type};return`<span class="type-badge type-${type}">${ti.label}: ${count}</span>`}).join('')}</div>`:''}</div><div class="project-card-footer"><div class="card-actions"><button class="btn btn-accent btn-sm" onclick="ProductManager.showDetail('${prod.id}')">👁️ جزئیات</button></div><button class="btn btn-danger btn-xs" onclick="ProductManager.removeProduct('${prod.id}')">🗑️</button></div></div>`});h+='</div>'}h+='</div></div>';h+=EnvironmentRenderer.getSectionHTML();board.innerHTML=h;const st=SectionCollapseManager.getState();Object.keys(st).forEach(id=>{const sec=document.getElementById(`section-${id}`);if(sec&&st[id])sec.classList.add('collapsed')})}};
/* ==================== ACTION REGISTRY ==================== */
const ActionRegistry={openProductSettings(){openSettingsModal()},saveProduct(){projectData.metadata.updatedAt=new Date().toISOString();const js=JSON.stringify(projectData.appData,null,2);const b=new Blob([js],{type:'application/json;charset=utf-8'});const u=URL.createObjectURL(b);const a=document.createElement('a');a.href=u;a.download=`${projectData.projectName}_v${projectData.version}_${new Date().toISOString().replace(/[:.]/g,'-').slice(0,19)}.json`;document.body.appendChild(a);a.click();document.body.removeChild(a);URL.revokeObjectURL(u);showToast('💾 ذخیره شد','success');WebhookManager.sendEvent('product_saved',{fileName:a.download})},loadProduct(){const inp=document.createElement('input');inp.type='file';inp.accept='.json';inp.addEventListener('change',e=>{const f=e.target.files[0];if(!f)return;const r=new FileReader();r.onload=ev=>{try{const ld=JSON.parse(ev.target.result);if(!validateAppDataStructure(ld)){showToast('❌ ساختار نامعتبر','error');return}HistoryManager.pushState();projectData.appData=ld;updateProjectUI();renderToolGroups();ToolbarManager.populateEnvironmentFilter();refreshUI();showToast('📂 بارگذاری شد','success')}catch(err){showToast('❌ خطا: '+err.message,'error')}};r.readAsText(f)});inp.click()},clearAll(){if(!confirm('⚠️ پاکسازی کامل؟ این عمل غیرقابل بازگشت است!'))return;if(!confirm('⚠️ تأیید نهایی؟'))return;HistoryManager.pushState();projectData.appData={environments:[],products:[]};ToolbarManager.populateEnvironmentFilter();refreshUI();showToast('🗑️ پاکسازی شد','success')},showGalaxyChart(){GalaxyChartManager.open()},showExecutiveDashboard(){DashboardManager.open()},showDependencyAnalysis(){DependencyAnalysisManager.open()},showDeploymentAnalysis(){DeploymentAnalysisManager.open()},showVersionAnalysis(){VersionAnalysisManager.open()},showRiskAnalysis(){RiskAnalysisManager.open()},defaultAction(tool){showToast(`ابزار: ${tool.title}`,'info')}};
/* ==================== WEBHOOK MANAGER ==================== */
const WebhookManager={getConfig(){return projectData.webhook},setConfig(c){projectData.webhook={...projectData.webhook,...c};this.updateStatusUI()},isValid(){const c=this.getConfig();return c.enabled&&c.url&&c.clientId&&/^https?:\/\//i.test(c.url)},updateStatusUI(){const c=this.getConfig();const d=document.getElementById('webhookStatusDot'),t=document.getElementById('webhookStatusText');if(!d||!t)return;d.className='webhook-status-dot';if(!c.enabled)t.textContent='وضعیت: غیرفعال';else if(!c.url||!c.clientId){d.classList.add('pending');t.textContent='وضعیت: ناقص'}else{d.classList.add('success');t.textContent='وضعیت: آماده'}},async sendEvent(en,payload={}){if(!this.isValid())return;const c=this.getConfig();try{await fetch(c.url,{method:'POST',headers:{'Content-Type':'application/json','X-Client-Id':c.clientId,'X-Event-Name':en},body:JSON.stringify({event:en,timestamp:new Date().toISOString(),projectId:projectData.projectId,data:payload})})}catch(e){}}};
/* ==================== ENVIRONMENT MANAGER ==================== */
const EnvironmentManager={currentEditEnvId:null,openAddModal(){document.getElementById('newEnvName').value='';document.getElementById('newEnvDesc').value='';document.getElementById('newEnvBgColor').value='#38a169';document.getElementById('newEnvTextColor').value='#ffffff';ModalManager.open('envManagerModal')},saveNewEnv(){const n=document.getElementById('newEnvName').value.trim();if(!n){showToast('نام الزامی است','error');return}if(projectData.appData.environments.some(e=>e.name.toLowerCase()===n.toLowerCase())){showToast('تکراری است','warning');return}HistoryManager.pushState();projectData.appData.environments.push({id:`env-${Date.now()}`,name:n,description:document.getElementById('newEnvDesc').value.trim()||'',backgroundColor:document.getElementById('newEnvBgColor').value,textColor:document.getElementById('newEnvTextColor').value});ModalManager.close('envManagerModal');ToolbarManager.populateEnvironmentFilter();refreshUI();showToast(`محیط "${n}" اضافه شد`,'success')},openEditModal(envId){const env=projectData.appData.environments.find(e=>e.id===envId);if(!env)return;this.currentEditEnvId=envId;document.getElementById('editEnvId').value=env.id;document.getElementById('editEnvName').value=env.name;document.getElementById('editEnvDesc').value=env.description||'';document.getElementById('editEnvBgColor').value=env.backgroundColor||'#38a169';document.getElementById('editEnvTextColor').value=env.textColor||'#ffffff';this.updateEditPreview();ModalManager.open('envEditModal')},updateEditPreview(){const p=document.getElementById('editEnvPreview');p.style.backgroundColor=document.getElementById('editEnvBgColor').value;p.style.color=document.getElementById('editEnvTextColor').value},saveEdit(){const env=projectData.appData.environments.find(e=>e.id===this.currentEditEnvId);if(!env)return;const nn=document.getElementById('editEnvName').value.trim();if(!nn){showToast('نام الزامی است','error');return}HistoryManager.pushState();env.name=nn;env.description=document.getElementById('editEnvDesc').value.trim();env.backgroundColor=document.getElementById('editEnvBgColor').value;env.textColor=document.getElementById('editEnvTextColor').value;ModalManager.close('envEditModal');ToolbarManager.populateEnvironmentFilter();refreshUI();showToast(`محیط "${env.name}" ویرایش شد`,'success')},remove(id){const env=projectData.appData.environments.find(e=>e.id===id);if(!env)return;if(!confirm(`حذف "${env.name}"؟`))return;HistoryManager.pushState();projectData.appData.environments=projectData.appData.environments.filter(e=>e.id!==id);ToolbarManager.populateEnvironmentFilter();refreshUI();showToast(`"${env.name}" حذف شد`,'warning')}};
/* ==================== PRODUCT MANAGER ==================== */
const ProductManager={currentDetailProductId:null,currentEditProjectId:null,currentDependencyProjectId:null,currentDependencyProjId:null,currentDeploymentProjId:null,currentDeploymentMode:null,currentDeploymentEnvId:null,currentEditDeploymentId:null,getTypeInfo(type){return VALID_PROJECT_TYPES.find(t=>t.value===type)||{value:type,label:type,icon:'❓'}},getEnvironmentName(envId){const e=projectData.appData.environments.find(x=>x.id===envId);return e?e.name:envId},getEnvironmentColors(envId){const e=projectData.appData.environments.find(x=>x.id===envId);return{backgroundColor:e?.backgroundColor||'#38a169',textColor:e?.textColor||'#ffffff'}},findProjectById(pid){for(const prod of projectData.appData.products){const pr=prod.projects.find(p=>p.id===pid);if(pr)return pr}return null},getActiveDeployments(proj){return(proj.deployments||[]).filter(d=>d.status==='active')},getEnvironmentsWithoutDeployment(proj){const allEnvIds=projectData.appData.environments.map(e=>e.id);const deployedEnvIds=this.getActiveDeployments(proj).map(d=>d.environmentId);return allEnvIds.filter(id=>!deployedEnvIds.includes(id))},showDetail(productId){const prod=projectData.appData.products.find(p=>p.id===productId);if(!prod)return;this.currentDetailProductId=productId;document.getElementById('projectDetailTitle').innerHTML=`<span>📦</span><span>${prod.title}</span>`;const projects=prod.projects||[];const totalDeploys=projects.reduce((s,pr)=>s+this.getActiveDeployments(pr).length,0);let html=`<div class="detail-section"><div class="detail-section-header"><span>ℹ️ اطلاعات محصول</span><div class="detail-section-header-actions"><button class="btn btn-accent btn-sm" onclick="ProductManager.openProductEditModal()">✏️ ویرایش</button></div></div><div class="detail-section-body"><div class="detail-info-grid"><div class="detail-info-item"><span class="detail-info-label">عنوان</span><span class="detail-info-value">${prod.title}</span></div><div class="detail-info-item"><span class="detail-info-label">توضیحات</span><span class="detail-info-value">${prod.description||'—'}</span></div><div class="detail-info-item"><span class="detail-info-label">تاریخ ایجاد</span><span class="detail-info-value">${prod.metadata?.createdAt?new Date(prod.metadata.createdAt).toLocaleDateString('fa-IR'):'—'}</span></div><div class="detail-info-item"><span class="detail-info-label">مالک</span><span class="detail-info-value">${prod.metadata?.owner||'—'}</span></div><div class="detail-info-item"><span class="detail-info-label">تعداد پروژه</span><span class="detail-info-value">${projects.length}</span></div><div class="detail-info-item"><span class="detail-info-label">استقرارهای فعال</span><span class="detail-info-value">${totalDeploys}</span></div></div></div></div><div class="detail-section"><div class="detail-section-header"><span>🌿 پروژه‌ها (${projects.length})</span><div class="detail-section-header-actions"><button class="btn btn-accent btn-sm" onclick="ProductManager.openAddProjectModal()">➕ افزودن</button></div></div><div class="detail-section-body">`;if(projects.length===0){html+='<div class="empty-subprojects">🌿 هیچ پروژه‌ای نیست</div>'}else{html+='<div class="subproject-list">';projects.forEach(pr=>{const ti=this.getTypeInfo(pr.type);const deps=pr.dependencies||[];const deploys=this.getActiveDeployments(pr);const emptyEnvs=this.getEnvironmentsWithoutDeployment(pr);const canAdd=emptyEnvs.length>0;html+=`<div class="subproject-card"><div class="subproject-header"><div class="subproject-title"><span>${ti.icon}</span><span>${pr.title}</span><span class="type-badge type-${pr.type}">${ti.label}</span></div><div class="subproject-actions"><button class="btn btn-ghost btn-sm" onclick="ProductManager.openDependencyManager('${prod.id}','${pr.id}')">🔗 وابستگی‌ها</button><button class="btn btn-ghost btn-sm" onclick="ProductManager.openProjectEditModal('${prod.id}','${pr.id}')">✏️</button><button class="btn btn-danger btn-sm" onclick="ProductManager.removeProject('${prod.id}','${pr.id}')">🗑️</button></div></div><div class="subproject-meta">${pr.description||''}</div><div class="detail-info-grid" style="margin-bottom:8px">${pr.metadata?.framework?`<div class="detail-info-item"><span class="detail-info-label">فریم‌ورک</span><span class="detail-info-value">${pr.metadata.framework}</span></div>`:''}${pr.metadata?.language?`<div class="detail-info-item"><span class="detail-info-label">زبان</span><span class="detail-info-value">${pr.metadata.language}</span></div>`:''}${deps.length>0?`<div class="detail-info-item"><span class="detail-info-label">وابستگی‌ها (${deps.length})</span><span class="detail-info-value">${deps.map(depId=>{const dp=this.findProjectById(depId);return dp?dp.title:depId}).join('، ')}</span></div>`:''}</div>`;html+=`<div class="deployment-section"><div class="deployment-header"><span class="deployment-header-title">🚀 استقرارهای فعال (${deploys.length})</span>${canAdd?`<button class="btn btn-accent btn-xs" onclick="ProductManager.openAddDeploymentModal('${prod.id}','${pr.id}')">➕ افزودن</button>`:'<span style="font-size:10px;color:var(--text-muted)">✓ کامل</span>'}</div>`;if(deploys.length===0){html+='<div class="empty-deployments">هنوز استقرار ندارد</div>'}else{html+='<div class="deployment-list">';deploys.forEach(d=>{const en=this.getEnvironmentName(d.environmentId);const cl=this.getEnvironmentColors(d.environmentId);const fd=d.deploymentDate?new Date(d.deploymentDate).toLocaleString('fa-IR'):'—';html+=`<div class="deployment-item"><div class="deployment-item-info"><span class="deployment-env-tag" style="background-color:${cl.backgroundColor};color:${cl.textColor}">${en}</span><span class="deployment-version">v${d.version}</span><span class="deployment-date">📅 ${fd}</span></div><div class="deployment-actions"><button class="btn btn-ghost btn-xs" onclick="ProductManager.openEditDeploymentModal('${prod.id}','${pr.id}','${d.id}')">✏️</button><button class="btn btn-danger btn-xs" onclick="ProductManager.removeDeployment('${prod.id}','${pr.id}','${d.id}')">🗑️</button></div>`;if(d.releaseNotes){html+=`<div class="deployment-notes">📝 ${d.releaseNotes}</div>`}if(d.requiredDeployments&&d.requiredDeployments.length>0){html+=`<div class="deployment-req">🔗 نیازمندی‌ها: ${d.requiredDeployments.map(rd=>{const rp=this.findProjectById(rd.projectId);let depVer='';if(rp){const rdDep=(rp.deployments||[]).find(x=>x.id===rd.deploymentId);if(rdDep)depVer=` (v${rdDep.version})`}return `${rp?rp.title:rd.projectId}${depVer}`}).join('، ')}</div>`}html+=`</div>`});html+='</div>'}html+=`</div></div>`});html+='</div>'}html+=`</div></div>`;document.getElementById('projectDetailBody').innerHTML=html;ModalManager.open('projectDetailModal')},openAddProductModal(){document.getElementById('newProductTitle').value='';document.getElementById('newProductDescription').value='';document.getElementById('newProductOwner').value='';document.getElementById('newProductCreatedAt').value=new Date().toLocaleDateString('fa-IR');ModalManager.open('addProductModal')},createNewProduct(){const title=document.getElementById('newProductTitle').value.trim();if(!title||title.length<3){showToast('عنوان الزامی است (حداقل ۳ کاراکتر)','error');return}if(projectData.appData.products.some(p=>p.title.toLowerCase()===title.toLowerCase())){showToast('تکراری است','warning');return}HistoryManager.pushState();projectData.appData.products.push({id:`prod-${Date.now()}`,title,description:document.getElementById('newProductDescription').value.trim()||'',metadata:{createdAt:new Date().toISOString(),owner:document.getElementById('newProductOwner').value.trim()||'',customFields:{}},projects:[]});ModalManager.close('addProductModal');refreshUI();showToast(`"${title}" ایجاد شد`,'success')},openDependencyManager(productId,projId){this.currentDependencyProjectId=productId;this.currentDependencyProjId=projId;this.renderCurrentDependencies();this.renderAllProjectsList();this.setupDependencySearch();ModalManager.open('dependencyManagerModal')},renderCurrentDependencies(){const prod=projectData.appData.products.find(p=>p.id===this.currentDependencyProjectId);if(!prod)return;const pr=prod.projects.find(p=>p.id===this.currentDependencyProjId);if(!pr)return;const c=document.getElementById('currentDependenciesList');const deps=pr.dependencies||[];if(deps.length===0){c.innerHTML='<div class="empty-dependencies">هیچ وابستگی‌ای نیست</div>';return}c.innerHTML=deps.map((depId,index)=>{const depPr=this.findProjectById(depId);const projectName=depPr?depPr.title:depId;const typeInfo=depPr?this.getTypeInfo(depPr.type):null;return`<div class="dependency-item"><div class="dependency-info"><div><div class="dependency-project-name">${typeInfo?typeInfo.icon+' ':''}${projectName}</div></div></div><button class="btn btn-danger btn-xs" onclick="ProductManager.removeDependency(${index})">🗑️</button></div>`}).join('')},removeDependency(index){const prod=projectData.appData.products.find(p=>p.id===this.currentDependencyProjectId);if(!prod)return;const pr=prod.projects.find(p=>p.id===this.currentDependencyProjId);if(!pr)return;if(!confirm('حذف این وابستگی؟'))return;HistoryManager.pushState();pr.dependencies.splice(index,1);ModalManager.close('dependencyManagerModal');refreshUI();if(this.currentDetailProductId)this.showDetail(this.currentDetailProductId);showToast('وابستگی حذف شد','success')},renderAllProjectsList(){const rc=document.getElementById('dependencySearchResults');const cp=projectData.appData.products.find(p=>p.id===this.currentDependencyProjectId);if(!cp)return;const cpr=cp.projects.find(p=>p.id===this.currentDependencyProjId);if(!cpr)return;const allProjects=[];projectData.appData.products.forEach(prod=>{prod.projects.forEach(pr=>{if(pr.id!==cpr.id)allProjects.push({...pr,productName:prod.title})})});if(allProjects.length===0){rc.innerHTML='<div class="empty-dependencies">پروژه دیگری نیست</div>';return}rc.innerHTML=allProjects.map(pr=>{const isDep=(cpr.dependencies||[]).includes(pr.id);return`<div class="dependency-project-result"><button class="dependency-project-add-btn" onclick="ProductManager.addDependency('${pr.id}')" ${isDep?'disabled style="opacity:.5"':''}><span>${this.getTypeInfo(pr.type).icon}</span><span>${pr.title}</span><small style="color:var(--text-muted);font-weight:normal">(${pr.productName})</small>${isDep?'<span style="margin-right:auto;font-size:10px;color:var(--success)">✓ وابسته</span>':''}</button></div>`}).join('')},setupDependencySearch(){const si=document.getElementById('dependencySearch');si.value='';si.oninput=e=>{const q=e.target.value.trim().toLowerCase();if(q.length===0){this.renderAllProjectsList();return}const cp=projectData.appData.products.find(p=>p.id===this.currentDependencyProjectId);if(!cp)return;const cpr=cp.projects.find(p=>p.id===this.currentDependencyProjId);if(!cpr)return;const allProjects=[];projectData.appData.products.forEach(prod=>{prod.projects.forEach(pr=>{if(pr.id!==cpr.id)allProjects.push({...pr,productName:prod.title})})});const fl=allProjects.filter(pr=>pr.title.toLowerCase().includes(q)||pr.productName.toLowerCase().includes(q)||(pr.description||'').toLowerCase().includes(q));const rc=document.getElementById('dependencySearchResults');if(fl.length===0){rc.innerHTML='<div class="empty-dependencies">نتیجه‌ای نیست</div>';return}rc.innerHTML=fl.map(pr=>{const isDep=(cpr.dependencies||[]).includes(pr.id);return`<div class="dependency-project-result"><button class="dependency-project-add-btn" onclick="ProductManager.addDependency('${pr.id}')" ${isDep?'disabled style="opacity:.5"':''}><span>${this.getTypeInfo(pr.type).icon}</span><span>${pr.title}</span><small style="color:var(--text-muted)">(${pr.productName})</small></button></div>`}).join('')}},addDependency(projectId){const prod=projectData.appData.products.find(p=>p.id===this.currentDependencyProjectId);if(!prod)return;const pr=prod.projects.find(p=>p.id===this.currentDependencyProjId);if(!pr)return;if((pr.dependencies||[]).includes(projectId)){showToast('تکراری است','warning');return}HistoryManager.pushState();if(!pr.dependencies)pr.dependencies=[];pr.dependencies.push(projectId);ModalManager.close('dependencyManagerModal');refreshUI();if(this.currentDetailProductId)this.showDetail(this.currentDetailProductId);showToast('وابستگی اضافه شد','success')},openProductEditModal(){const pid=this.currentDetailProductId;if(!pid)return;const prod=projectData.appData.products.find(p=>p.id===pid);if(!prod)return;document.getElementById('editProductId').value=prod.id;document.getElementById('editProductTitle').value=prod.title;document.getElementById('editProductDescription').value=prod.description||'';document.getElementById('editProductOwner').value=prod.metadata?.owner||'';document.getElementById('editProductCreatedAt').value=prod.metadata?.createdAt?new Date(prod.metadata.createdAt).toLocaleDateString('fa-IR'):'—';ModalManager.open('productEditModal')},saveProductEdit(){const pid=this.currentDetailProductId;if(!pid)return;const prod=projectData.appData.products.find(p=>p.id===pid);if(!prod)return;const nt=document.getElementById('editProductTitle').value.trim();if(!nt){showToast('عنوان الزامی است','error');return}HistoryManager.pushState();prod.title=nt;prod.description=document.getElementById('editProductDescription').value.trim();if(!prod.metadata)prod.metadata={};prod.metadata.owner=document.getElementById('editProductOwner').value.trim();prod.metadata.updatedAt=new Date().toISOString();ModalManager.close('productEditModal');refreshUI();this.showDetail(pid);showToast(`"${prod.title}" ویرایش شد`,'success')},openAddProjectModal(){if(!this.currentDetailProductId){showToast('ابتدا محصول انتخاب کنید','warning');return}document.getElementById('newProjectTitle').value='';document.getElementById('newProjectType').value='';document.getElementById('newProjectFramework').value='';document.getElementById('newProjectLanguage').value='';document.getElementById('newProjectRepo').value='';document.getElementById('newProjectDescription').value='';populateProjectTypeDropdowns();ModalManager.open('addProjectModal')},saveNewProject(){const pid=this.currentDetailProductId;if(!pid)return;const prod=projectData.appData.products.find(p=>p.id===pid);if(!prod)return;const title=document.getElementById('newProjectTitle').value.trim();const type=document.getElementById('newProjectType').value;if(!title||!type){showToast('عنوان و نوع الزامی است','error');return}HistoryManager.pushState();const np={id:`proj-${Date.now()}`,title,description:document.getElementById('newProjectDescription').value.trim()||'',type,repositoryUrl:document.getElementById('newProjectRepo').value.trim(),metadata:{createdAt:new Date().toISOString(),customFields:{}},dependencies:[],deployments:[]};const fw=document.getElementById('newProjectFramework').value.trim();if(fw)np.metadata.framework=fw;const lg=document.getElementById('newProjectLanguage').value.trim();if(lg)np.metadata.language=lg;prod.projects.push(np);ModalManager.close('addProjectModal');refreshUI();this.showDetail(pid);showToast(`"${title}" ایجاد شد`,'success')},openProjectEditModal(productId,projId){const prod=projectData.appData.products.find(p=>p.id===productId);if(!prod)return;const pr=prod.projects.find(p=>p.id===projId);if(!pr)return;this.currentEditProjectId=projId;populateProjectTypeDropdowns();document.getElementById('editProjId').value=pr.id;document.getElementById('editProjTitle').value=pr.title;document.getElementById('editProjType').value=pr.type;document.getElementById('editProjFramework').value=pr.metadata?.framework||'';document.getElementById('editProjLanguage').value=pr.metadata?.language||'';document.getElementById('editProjRepo').value=pr.repositoryUrl||'';document.getElementById('editProjDescription').value=pr.description||'';ModalManager.open('editProjectModal')},saveProjectEdit(){const pid=this.currentDetailProductId;const projId=this.currentEditProjectId;if(!pid||!projId)return;const prod=projectData.appData.products.find(p=>p.id===pid);if(!prod)return;const pr=prod.projects.find(p=>p.id===projId);if(!pr)return;const nt=document.getElementById('editProjTitle').value.trim();const nty=document.getElementById('editProjType').value;if(!nt||!nty){showToast('عنوان و نوع الزامی است','error');return}HistoryManager.pushState();pr.title=nt;pr.type=nty;pr.description=document.getElementById('editProjDescription').value.trim()||'';pr.repositoryUrl=document.getElementById('editProjRepo').value.trim();if(!pr.metadata)pr.metadata={};const fw=document.getElementById('editProjFramework').value.trim();if(fw)pr.metadata.framework=fw;else delete pr.metadata.framework;const lg=document.getElementById('editProjLanguage').value.trim();if(lg)pr.metadata.language=lg;else delete pr.metadata.language;pr.metadata.updatedAt=new Date().toISOString();ModalManager.close('editProjectModal');refreshUI();this.showDetail(pid);showToast(`"${pr.title}" ویرایش شد`,'success')},openAddDeploymentModal(productId,projId){if(!productId||!projId)return;const prod=projectData.appData.products.find(p=>p.id===productId);if(!prod)return;const pr=prod.projects.find(p=>p.id===projId);if(!pr)return;const emptyEnvs=this.getEnvironmentsWithoutDeployment(pr);if(emptyEnvs.length===0){showToast('همه محیط‌ها استقرار دارند','warning');return}this.currentDeploymentProjId=projId;this.currentDeploymentMode='add';this.currentDeploymentEnvId=null;this.currentEditDeploymentId=null;document.getElementById('deploymentModalTitle').innerHTML='<span>🚀</span><span>افزودن نسخه استقرار</span>';const envGroup=document.getElementById('deploymentEnvGroup');const envs=projectData.appData.environments.filter(e=>emptyEnvs.includes(e.id));envGroup.innerHTML=`<label class="form-label">محیط استقرار <span class="required">*</span></label><select class="form-select" id="depEnvironment" onchange="ProductManager.onEnvChange()">${envs.map(e=>{const cl=this.getEnvironmentColors(e.id);return`<option value="${e.id}" style="background-color:${cl.backgroundColor};color:${cl.textColor}">${e.name} - ${e.description}</option>`}).join('')}</select>`;document.getElementById('depVersion').value='';document.getElementById('depDate').value='';document.getElementById('depReleaseNotes').value='';this.renderRequiredDeploymentsSection();ModalManager.open('deploymentModal')},openEditDeploymentModal(productId,projId,depId){if(!productId||!projId||!depId)return;const prod=projectData.appData.products.find(p=>p.id===productId);if(!prod)return;const pr=prod.projects.find(p=>p.id===projId);if(!pr)return;const dep=(pr.deployments||[]).find(d=>d.id===depId);if(!dep)return;this.currentDeploymentProjId=projId;this.currentDeploymentMode='edit';this.currentDeploymentEnvId=dep.environmentId;this.currentEditDeploymentId=depId;document.getElementById('deploymentModalTitle').innerHTML='<span>✏️</span><span>ویرایش نسخه استقرار</span>';const envGroup=document.getElementById('deploymentEnvGroup');const cl=this.getEnvironmentColors(dep.environmentId);envGroup.innerHTML=`<label class="form-label">محیط استقرار <span class="readonly-badge">فقط خواندنی</span></label><div class="env-read-only-tag" style="background-color:${cl.backgroundColor};color:${cl.textColor}">${this.getEnvironmentName(dep.environmentId)}</div>`;document.getElementById('depVersion').value=dep.version;let dv='';if(dep.deploymentDate){const d=new Date(dep.deploymentDate);const pad=n=>n.toString().padStart(2,'0');dv=`${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`}document.getElementById('depDate').value=dv;document.getElementById('depReleaseNotes').value=dep.releaseNotes||'';this.renderRequiredDeploymentsSection(dep);ModalManager.open('deploymentModal')},onEnvChange(){this.renderRequiredDeploymentsSection()},renderRequiredDeploymentsSection(existingDep){const section=document.getElementById('requiredDeploymentsSection');const envSelect=document.getElementById('depEnvironment');const envId=this.currentDeploymentMode==='edit'?this.currentDeploymentEnvId:(envSelect?envSelect.value:null);if(!envId){section.innerHTML='';return}const prod=projectData.appData.products.find(p=>p.id===this.currentDetailProductId);if(!prod)return;const pr=prod.projects.find(p=>p.id===this.currentDeploymentProjId);if(!pr)return;const deps=pr.dependencies||[];if(deps.length===0){section.innerHTML='';return}let h='<div class="form-group"><label class="form-label">🔗 استقرارهای مورد نیاز (به ازای هر وابستگی) <span class="required">*</span></label><div class="form-hint">برای هر وابستگی، یک استقرار از آن پروژه در محیط "'+this.getEnvironmentName(envId)+'" انتخاب کنید</div>';deps.forEach(depId=>{const depPr=this.findProjectById(depId);if(!depPr)return;const depDeployments=(depPr.deployments||[]).filter(d=>d.status==='active'&&d.environmentId===envId);const existingReq=existingDep?(existingDep.requiredDeployments||[]).find(r=>r.projectId===depId):null;h+=`<div class="req-deployment-box"><div class="req-deployment-title">📦 ${depPr.title}</div>`;if(depDeployments.length===0){h+=`<div style="color:var(--danger);font-size:11px">⚠️ هیچ استقرار فعالی در ${this.getEnvironmentName(envId)} ندارد. ابتدا یک استقرار برای این پروژه ثبت کنید.</div>`}else{h+=`<select class="req-deployment-select" data-dep-project="${depId}">${depDeployments.map(d=>`<option value="${d.id}" ${existingReq&&existingReq.deploymentId===d.id?'selected':''}>v${d.version} - ${d.deploymentDate?new Date(d.deploymentDate).toLocaleDateString('fa-IR'):''}</option>`).join('')}</select>`}h+='</div>'});h+='</div>';section.innerHTML=h},saveDeployment(){const pid=this.currentDetailProductId;const projId=this.currentDeploymentProjId;if(!pid||!projId){showToast('خطا: محصول یا پروژه مشخص نشده','error');return}const prod=projectData.appData.products.find(p=>p.id===pid);if(!prod)return;const pr=prod.projects.find(p=>p.id===projId);if(!pr)return;const version=document.getElementById('depVersion').value.trim();const date=document.getElementById('depDate').value;const releaseNotes=document.getElementById('depReleaseNotes').value.trim();if(!version){showToast('نسخه الزامی است','error');return}if(!date){showToast('تاریخ استقرار الزامی است','error');return}if(!releaseNotes){showToast('ریلیز نوت الزامی است','error');return}const deploymentDate=new Date(date).toISOString();HistoryManager.pushState();if(!pr.deployments)pr.deployments=[];const requiredDeployments=[];const reqSelects=document.querySelectorAll('.req-deployment-select');reqSelects.forEach(sel=>{const depProjectId=sel.dataset.depProject;const depDeploymentId=sel.value;if(depProjectId&&depDeploymentId){requiredDeployments.push({projectId:depProjectId,deploymentId:depDeploymentId})}});if(this.currentDeploymentMode==='add'){const envId=document.getElementById('depEnvironment').value;if(!envId){showToast('محیط را انتخاب کنید','error');return}const newDep={id:`dep-${Date.now()}`,environmentId:envId,version,deploymentDate,releaseNotes,requiredDeployments,status:'active'};pr.deployments.push(newDep);showToast(`v${version} در ${this.getEnvironmentName(envId)} استقرار یافت`,'success');WebhookManager.sendEvent('deployment_added',{productId:pid,projId,envId,version})}else{const depId=this.currentEditDeploymentId;const dep=pr.deployments.find(d=>d.id===depId);if(dep){dep.version=version;dep.deploymentDate=deploymentDate;dep.releaseNotes=releaseNotes;dep.requiredDeployments=requiredDeployments;showToast(`استقرار ویرایش شد`,'success');WebhookManager.sendEvent('deployment_updated',{productId:pid,projId,version})}}ModalManager.close('deploymentModal');refreshUI();this.showDetail(pid)},removeDeployment(productId,projId,depId){if(!confirm('حذف این استقرار؟'))return;const prod=projectData.appData.products.find(p=>p.id===productId);if(!prod)return;const pr=prod.projects.find(p=>p.id===projId);if(!pr)return;const dep=(pr.deployments||[]).find(d=>d.id===depId);if(!dep)return;HistoryManager.pushState();dep.status='removed';refreshUI();this.showDetail(productId);showToast('استقرار حذف شد','warning')},removeProduct(productId){const prod=projectData.appData.products.find(p=>p.id===productId);if(!prod)return;if(!confirm(`حذف "${prod.title}"؟`))return;HistoryManager.pushState();projectData.appData.products=projectData.appData.products.filter(p=>p.id!==productId);refreshUI();ModalManager.close('projectDetailModal');showToast(`"${prod.title}" حذف شد`,'warning')},removeProject(productId,projId){const prod=projectData.appData.products.find(p=>p.id===productId);if(!prod)return;const pr=prod.projects.find(p=>p.id===projId);if(!pr)return;if(!confirm(`حذف "${pr.title}"؟`))return;HistoryManager.pushState();prod.projects=prod.projects.filter(p=>p.id!==projId);refreshUI();if(this.currentDetailProductId===productId)this.showDetail(productId);showToast(`"${pr.title}" حذف شد`,'warning')}};
/* ==================== GALAXY CHART ==================== */
const GalaxyChartManager={svg:null,zoomBehavior:null,NODE_COLORS:{product:'#c9a961',backend:'#4299e1',frontend:'#48bb78',service:'#ecc94b',database:'#ed64a6',mobile:'#9f7aea',infrastructure:'#ed8936'},open(){ModalManager.open('galaxyChartModal');setTimeout(()=>this.render(),150)},render(){const c=document.getElementById('galaxyChartContainer');c.innerHTML='';const w=c.clientWidth||900,h=c.clientHeight||600;const nodes=[],links=[];projectData.appData.products.forEach(prod=>{nodes.push({id:prod.id,label:prod.title,type:'product',radius:30,data:prod});prod.projects.forEach(pr=>{nodes.push({id:pr.id,label:pr.title,type:'project',projectType:pr.type,radius:16,data:pr});links.push({source:prod.id,target:pr.id,type:'ownership'});(pr.dependencies||[]).forEach(depId=>{links.push({source:pr.id,target:depId,type:'dependency'})})})});if(nodes.length===0){c.innerHTML='<div class="galaxy-empty"><div style="font-size:60px">🌌</div><div>هیچ داده‌ای نیست</div></div>';return}const svg=d3.select(c).append('svg').attr('width',w).attr('height',h).attr('id','galaxySvg');this.svg=svg;svg.append('rect').attr('width',w).attr('height',h).attr('fill','#0a0e27');for(let i=0;i<100;i++)svg.append('circle').attr('cx',Math.random()*w).attr('cy',Math.random()*h).attr('r',Math.random()+.3).attr('fill','#fff').attr('opacity',Math.random()*.5+.1);const defs=svg.append('defs');const gf=defs.append('filter').attr('id','galaxyGlow');gf.append('feGaussianBlur').attr('stdDeviation','3').attr('result','blur');const fm=gf.append('feMerge');fm.append('feMergeNode').attr('in','blur');fm.append('feMergeNode').attr('in','SourceGraphic');const zg=svg.append('g');this.zoomBehavior=d3.zoom().scaleExtent([.2,5]).on('zoom',e=>{zg.attr('transform',e.transform);document.getElementById('galaxyZoomIndicator').textContent=`🔍 ${Math.round(e.transform.k*100)}%`});svg.call(this.zoomBehavior);const sim=d3.forceSimulation(nodes).force('link',d3.forceLink(links).id(d=>d.id).distance(d=>d.type==='ownership'?100:160)).force('charge',d3.forceManyBody().strength(-300)).force('center',d3.forceCenter(w/2,h/2));const link=zg.append('g').selectAll('line').data(links).join('line').attr('stroke',d=>d.type==='dependency'?'#e0c789':'#4a5568').attr('stroke-width',d=>d.type==='dependency'?2:1).attr('stroke-opacity',.6).attr('stroke-dasharray',d=>d.type==='dependency'?'5,3':'none');const node=zg.append('g').selectAll('g').data(nodes).join('g').style('cursor','pointer').call(d3.drag().on('start',(e,d)=>{if(!e.active)sim.alphaTarget(.3).restart();d.fx=d.x;d.fy=d.y}).on('drag',(e,d)=>{d.fx=e.x;d.fy=e.y}).on('end',(e,d)=>{if(!e.active)sim.alphaTarget(0);d.fx=null;d.fy=null}));node.append('circle').attr('r',d=>d.radius).attr('fill',d=>d.type==='product'?this.NODE_COLORS.product:(this.NODE_COLORS[d.projectType]||'#718096')).attr('stroke','#fff').attr('stroke-width',1.5).attr('filter','url(#galaxyGlow)');node.append('text').attr('dy',d=>d.radius+14).attr('text-anchor','middle').attr('fill','#fff').attr('font-size','10px').attr('font-family','Vazirmatn').text(d=>d.label);node.on('click',(e,d)=>{const p=document.getElementById('galaxyInfoPanel');const ic=document.getElementById('galaxyInfoContent');let ih=`<h4>${d.label}</h4>`;if(d.type==='product'){ih+=`<div class="galaxy-info-row"><span class="galaxy-info-label">پروژه‌ها</span><span class="galaxy-info-value">${(d.data.projects||[]).length}</span></div>`}else{ih+=`<div class="galaxy-info-row"><span class="galaxy-info-label">نوع</span><span class="galaxy-info-value">${this.getTypeLabel(d.projectType)}</span></div><div class="galaxy-info-row"><span class="galaxy-info-label">استقرارها</span><span class="galaxy-info-value">${ProductManager.getActiveDeployments(d.data).length}</span></div>`}ic.innerHTML=ih;p.classList.add('visible')});sim.on('tick',()=>{link.attr('x1',d=>d.source.x).attr('y1',d=>d.source.y).attr('x2',d=>d.target.x).attr('y2',d=>d.target.y);node.attr('transform',d=>`translate(${d.x},${d.y})`)});this.renderLegend();this.updateStats(nodes,links)},getTypeLabel(t){const i=VALID_PROJECT_TYPES.find(x=>x.value===t);return i?i.label.split(' ')[0]:t},renderLegend(){const l=document.getElementById('galaxyLegend');let h='<div class="galaxy-legend-title">🎨 راهنما</div>';h+=`<div class="galaxy-legend-item"><div class="galaxy-legend-color" style="background:${this.NODE_COLORS.product}"></div><span>📦 محصول</span></div>`;VALID_PROJECT_TYPES.forEach(t=>{h+=`<div class="galaxy-legend-item"><div class="galaxy-legend-color" style="background:${this.NODE_COLORS[t.value]}"></div><span>${t.icon} ${t.label.split(' ')[0]}</span></div>`});l.innerHTML=h},updateStats(nodes,links){const p=projectData.appData.products.length;const pr=projectData.appData.products.reduce((x,p)=>x+p.projects.length,0);const dp=links.filter(l=>l.type==='dependency').length;document.getElementById('galaxyStats').innerHTML=`<span>📦 ${p}</span><span>🌿 ${pr}</span><span>🔗 ${dp}</span>`}};
/* ==================== DASHBOARD MANAGER ==================== */
const DashboardManager={open(){ModalManager.open('executiveDashboardModal');setTimeout(()=>this.render(),150)},render(){const c=document.getElementById('dashboardContainer');if(!c)return;const d=DashboardSectionRenderer.getData();const ps=projectData.appData.products||[];let typeCounts={};let envDeployCounts={};let pwd=0,pwod=0;ps.forEach(prod=>{(prod.projects||[]).forEach(pr=>{typeCounts[pr.type]=(typeCounts[pr.type]||0)+1;const active=(pr.deployments||[]).filter(x=>x.status==='active');active.forEach(dep=>{envDeployCounts[dep.environmentId]=(envDeployCounts[dep.environmentId]||0)+1});if(active.length>0)pwd++;else pwod++})});let h='<div class="dashboard-kpi-row">';h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">📦</div><div class="dashboard-kpi-value">${d.products}</div><div class="dashboard-kpi-label">محصول</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🌿</div><div class="dashboard-kpi-value">${d.projects}</div><div class="dashboard-kpi-label">پروژه</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🔗</div><div class="dashboard-kpi-value">${d.deps}</div><div class="dashboard-kpi-label">وابستگی</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🚀</div><div class="dashboard-kpi-value">${d.deploys}</div><div class="dashboard-kpi-label">استقرار</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🌍</div><div class="dashboard-kpi-value">${d.environments}</div><div class="dashboard-kpi-label">محیط</div></div>`;h+='</div><div class="dashboard-charts-grid">';h+='<div class="dashboard-chart-card"><div class="dashboard-chart-header">🥧 توزیع نوع پروژه‌ها</div><div class="dashboard-chart-body" id="dashTypeChart"></div></div>';h+='<div class="dashboard-chart-card"><div class="dashboard-chart-header">🌍 استقرارها به تفکیک محیط</div><div class="dashboard-chart-body" id="dashEnvChart"></div></div>';h+='<div class="dashboard-chart-card"><div class="dashboard-chart-header">✅ وضعیت استقرار</div><div class="dashboard-chart-body" id="dashStatusChart"></div></div>';h+='</div>';c.innerHTML=h;
// Type Donut
const typeEntries=Object.entries(typeCounts);if(typeEntries.length>0){const w=260,hh=200,r=Math.min(w,hh)/2-10;const svg=d3.select('#dashTypeChart').append('svg').attr('width',w).attr('height',hh).append('g').attr('transform',`translate(${w/2},${hh/2})`);const pie=d3.pie().value(d=>d[1]).sort(null);const arc=d3.arc().innerRadius(r*.55).outerRadius(r);const arcH=d3.arc().innerRadius(r*.55).outerRadius(r+6);const total=typeEntries.reduce((s,e)=>s+e[1],0);const colors={backend:'#4299e1',frontend:'#48bb78',service:'#ecc94b',database:'#ed64a6',mobile:'#9f7aea',infrastructure:'#ed8936'};svg.selectAll('path').data(pie(typeEntries)).join('path').attr('d',arc).attr('fill',d=>colors[d.data[0]]||'#718096').attr('stroke','#fff').attr('stroke-width',2).style('cursor','pointer').on('mouseover',function(e,d){d3.select(this).transition().duration(150).attr('d',arcH(d));showD3Tooltip(e,`${d.data[0]}: ${d.data[1]} (${Math.round(d.data[1]/total*100)}%)`)}).on('mousemove',e=>showD3Tooltip(e,`${d.data[0]}: ${d.data[1]}`)).on('mouseout',function(){d3.select(this).transition().duration(150).attr('d',arc);hideD3Tooltip()});svg.append('text').attr('text-anchor','middle').attr('dy','-.2em').attr('fill','var(--text)').attr('font-size','22px').attr('font-weight','800').text(total);svg.append('text').attr('text-anchor','middle').attr('dy','1.2em').attr('fill','var(--text-muted)').attr('font-size','10px').text('پروژه')}
// Env Bar
const envEntries=Object.entries(envDeployCounts);if(envEntries.length>0){const w=300,hh=200,m={top:10,right:10,bottom:40,left:30};const iw=w-m.left-m.right,ih=hh-m.top-m.bottom;const svg=d3.select('#dashEnvChart').append('svg').attr('width',w).attr('height',hh).append('g').attr('transform',`translate(${m.left},${m.top})`);const x=d3.scaleBand().domain(envEntries.map(d=>projectData.appData.environments.find(e=>e.id===d[0])?.name||d[0])).range([0,iw]).padding(.3);const y=d3.scaleLinear().domain([0,d3.max(envEntries,d=>d[1])||1]).nice().range([ih,0]);svg.append('g').attr('transform',`translate(0,${ih})`).call(d3.axisBottom(x).tickSize(0)).selectAll('text').attr('fill','var(--text-muted)').attr('font-size','9px').attr('font-family','Vazirmatn');svg.append('g').call(d3.axisLeft(y).ticks(4).tickSize(-iw)).selectAll('text').attr('fill','var(--text-muted)').attr('font-size','9px');svg.selectAll('rect').data(envEntries).join('rect').attr('x',(d,i)=>x(projectData.appData.environments.find(e=>e.id===d[0])?.name||d[0])).attr('y',d=>y(d[1])).attr('width',x.bandwidth()).attr('height',d=>ih-y(d[1])).attr('fill',projectData.appData.environments.find(e=>e.id===envEntries.find(x=>x[1]===d[1])?.[0])?.backgroundColor||'#38a169').attr('rx',4).style('cursor','pointer').on('mouseover',function(e,d){d3.select(this).attr('fill','#c9a961');showD3Tooltip(e,`${projectData.appData.environments.find(e2=>e2.id===d[0])?.name||d[0]}: ${d[1]}`)}).on('mousemove',e=>showD3Tooltip(e,`${d[1]}`)).on('mouseout',function(e,d){d3.select(this).attr('fill',projectData.appData.environments.find(e2=>e2.id===d[0])?.backgroundColor||'#38a169');hideD3Tooltip()});svg.selectAll('.bar-label').data(envEntries).join('text').attr('x',(d,i)=>x(projectData.appData.environments.find(e=>e.id===d[0])?.name||d[0])+x.bandwidth()/2).attr('y',d=>y(d[1])-5).attr('text-anchor','middle').attr('fill','var(--text)').attr('font-size','10px').attr('font-weight','700').text(d=>d[1])}
// Status Donut
if(pwd+pwod>0){const w=260,hh=200,r=Math.min(w,hh)/2-10;const svg=d3.select('#dashStatusChart').append('svg').attr('width',w).attr('height',hh).append('g').attr('transform',`translate(${w/2},${hh/2})`);const data=[{label:'با استقرار',value:pwd,color:'#38a169'},{label:'بدون استقرار',value:pwod,color:'#e53e3e'}];const pie=d3.pie().value(d=>d.value).sort(null);const arc=d3.arc().innerRadius(r*.6).outerRadius(r);const arcH=d3.arc().innerRadius(r*.6).outerRadius(r+6);const total=pwd+pwod;svg.selectAll('path').data(pie(data)).join('path').attr('d',arc).attr('fill',d=>d.data.color).attr('stroke','#fff').attr('stroke-width',2).style('cursor','pointer').on('mouseover',function(e,d){d3.select(this).transition().duration(150).attr('d',arcH(d));showD3Tooltip(e,`${d.data.label}: ${d.data.value} (${Math.round(d.data.value/total*100)}%)`)}).on('mousemove',e=>showD3Tooltip(e,`${d.data.label}: ${d.data.value}`)).on('mouseout',function(){d3.select(this).transition().duration(150).attr('d',arc);hideD3Tooltip()});const cov=Math.round(pwd/total*100);svg.append('text').attr('text-anchor','middle').attr('dy','-.2em').attr('fill',cov>=70?'#38a169':cov>=40?'#dd6b20':'#e53e3e').attr('font-size','24px').attr('font-weight','800').text(`${cov}%`);svg.append('text').attr('text-anchor','middle').attr('dy','1.2em').attr('fill','var(--text-muted)').attr('font-size','10px').text('پوشش')}
document.getElementById('dashboardFooterStats').innerHTML=`<span>📦 محصولات: <span class="stat-highlight">${d.products}</span></span><span>🌿 پروژه‌ها: <span class="stat-highlight">${d.projects}</span></span><span>🚀 استقرارها: <span class="stat-highlight">${d.deploys}</span></span><span>📈 پوشش: <span class="stat-highlight">${pwd+pwod>0?Math.round(pwd/(pwd+pwod)*100):0}%</span></span>`;document.getElementById('dashboardRefresh').onclick=()=>this.render()}};
/* ==================== ANALYSIS MANAGERS ==================== */
const DependencyAnalysisManager={open(){ModalManager.open('dependencyAnalysisModal');setTimeout(()=>this.render(),150)},render(){const c=document.getElementById('dependencyAnalysisContainer');if(!c)return;let totalDeps=0,totalProjects=0,brokenDeps=0;const allProjects=[];projectData.appData.products.forEach(prod=>{prod.projects.forEach(pr=>{allProjects.push(pr);totalProjects++;(pr.dependencies||[]).forEach(depId=>{totalDeps++;if(!ProductManager.findProjectById(depId))brokenDeps++})})});const inDeps={};allProjects.forEach(pr=>{(pr.dependencies||[]).forEach(depId=>{if(ProductManager.findProjectById(depId))inDeps[depId]=(inDeps[depId]||0)+1})});const bottlenecks=Object.entries(inDeps).filter(([k,v])=>v>=2);const isolated=allProjects.filter(pr=>(pr.dependencies||[]).length===0&&!(inDeps[pr.id]>0));let h='<div class="dashboard-kpi-row">';h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🔗</div><div class="dashboard-kpi-value">${totalDeps}</div><div class="dashboard-kpi-label">کل وابستگی‌ها</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">📦</div><div class="dashboard-kpi-value">${totalProjects}</div><div class="dashboard-kpi-label">پروژه‌ها</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">⚠️</div><div class="dashboard-kpi-value">${bottlenecks.length}</div><div class="dashboard-kpi-label">گلوگاه</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🔴</div><div class="dashboard-kpi-value">${brokenDeps}</div><div class="dashboard-kpi-label">شکسته</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🔵</div><div class="dashboard-kpi-value">${isolated.length}</div><div class="dashboard-kpi-label">ایزوله</div></div>`;h+='</div><div class="dashboard-charts-grid">';h+='<div class="dashboard-chart-card"><div class="dashboard-chart-header">📊 پروژه‌های گلوگاه (بیشترین وابستگی ورودی)</div><div class="dashboard-chart-body" id="depHubChart"></div></div>';h+='<div class="dashboard-chart-card"><div class="dashboard-chart-header">⚠️ لیست ریسک‌ها</div><div class="dashboard-chart-body" id="depRiskList" style="align-items:flex-start;overflow-y:auto;max-height:280px;width:100%;"></div></div>';h+='</div>';c.innerHTML=h;
// Hub Bar Chart
if(bottlenecks.length>0){const sorted=[...bottlenecks].sort((a,b)=>b[1]-a[1]).slice(0,8);const w=300,hh=200,m={top:10,right:10,bottom:40,left:120};const iw=w-m.left-m.right,ih=hh-m.top-m.bottom;const svg=d3.select('#depHubChart').append('svg').attr('width',w).attr('height',hh).append('g').attr('transform',`translate(${m.left},${m.top})`);const x=d3.scaleLinear().domain([0,d3.max(sorted,d=>d[1])||1]).range([0,iw]);const y=d3.scaleBand().domain(sorted.map(d=>{const pr=ProductManager.findProjectById(d[0]);return pr?pr.title:d[0]})).range([0,sorted.length*36]).padding(.2);svg.selectAll('rect').data(sorted).join('rect').attr('x',0).attr('y',d=>y(ProductManager.findProjectById(d[0])?.title||d[0])).attr('width',d=>x(d[1])).attr('height',y.bandwidth()).attr('fill',d=>d[1]>=3?'#e53e3e':'#dd6b20').attr('rx',4).style('cursor','pointer').on('mouseover',function(e,d){d3.select(this).attr('fill','#c9a961');const pr=ProductManager.findProjectById(d[0]);showD3Tooltip(e,`${pr?pr.title:d[0]}: ${d[1]} وابستگی ورودی`)}).on('mousemove',e=>showD3Tooltip(e,`${d[1]}`)).on('mouseout',function(e,d){d3.select(this).attr('fill',d[1]>=3?'#e53e3e':'#dd6b20');hideD3Tooltip()});svg.selectAll('.value').data(sorted).join('text').attr('x',d=>x(d[1])+6).attr('y',d=>y(ProductManager.findProjectById(d[0])?.title||d[0])+y.bandwidth()/2).attr('dy','.35em').attr('fill','var(--text-muted)').attr('font-size','10px').attr('font-weight','700').text(d=>d[1])}else{document.getElementById('depHubChart').innerHTML='<div class="dashboard-empty-chart"><span>✅</span><div>هیچ گلوگاهی شناسایی نشد</div></div>'}
// Risk List
let riskHtml='';if(brokenDeps>0){riskHtml+=`<div style="padding:10px 12px;background:rgba(229,62,62,.1);border-radius:6px;margin-bottom:8px;border-right:4px solid #e53e3e"><div style="font-weight:700;font-size:12px">🔴 وابستگی‌های شکسته: ${brokenDeps} مورد</div></div>`}if(bottlenecks.length>0){bottlenecks.forEach(([pid,cnt])=>{const pr=ProductManager.findProjectById(pid);riskHtml+=`<div style="padding:10px 12px;background:rgba(221,107,32,.1);border-radius:6px;margin-bottom:8px;border-right:4px solid #dd6b20"><div style="font-weight:700;font-size:12px">⚠️ گلوگاه: ${pr?pr.title:pid}</div><div style="font-size:11px;color:var(--text-muted)">${cnt} پروژه به این پروژه وابسته‌اند</div></div>`})}if(isolated.length>0){isolated.forEach(pr=>{riskHtml+=`<div style="padding:10px 12px;background:rgba(49,130,206,.1);border-radius:6px;margin-bottom:8px;border-right:4px solid #3182ce"><div style="font-weight:700;font-size:12px">🔵 ایزوله: ${pr.title}</div><div style="font-size:11px;color:var(--text-muted)">هیچ وابستگی ورودی یا خروجی ندارد</div></div>`})}if(!riskHtml){riskHtml='<div class="dashboard-empty-chart"><span>✅</span><div>هیچ ریسکی شناسایی نشد</div></div>'}document.getElementById('depRiskList').innerHTML=riskHtml;
document.getElementById('depAnalysisFooterStats').innerHTML=`<span>🔗 کل: <span class="stat-highlight">${totalDeps}</span></span><span>⚠️ گلوگاه: <span class="stat-highlight">${bottlenecks.length}</span></span><span>🔴 شکسته: <span class="stat-highlight">${brokenDeps}</span></span>`;document.getElementById('depAnalysisRefresh').onclick=()=>this.render()}};
const DeploymentAnalysisManager={open(){ModalManager.open('deploymentAnalysisModal');setTimeout(()=>this.render(),150)},render(){const c=document.getElementById('deploymentAnalysisContainer');if(!c)return;let total=0,pwd=0,pwod=0,withNotes=0,withoutNotes=0;const envCounts={};const oldDeploys=[];const now=new Date();const thirtyDaysAgo=new Date(now.getTime()-30*24*60*60*1000);projectData.appData.products.forEach(prod=>{prod.projects.forEach(pr=>{const active=(pr.deployments||[]).filter(d=>d.status==='active');if(active.length>0)pwd++;else pwod++;active.forEach(d=>{total++;if(d.releaseNotes)withNotes++;else withoutNotes++;envCounts[d.environmentId]=(envCounts[d.environmentId]||0)+1;if(d.deploymentDate&&new Date(d.deploymentDate)<thirtyDaysAgo){oldDeploys.push({project:pr.title,env:d.environmentId,version:d.version,daysOld:Math.floor((now-new Date(d.deploymentDate))/(1000*60*60*24))})}})})});const coverage=(pwd+pwod)>0?Math.round(pwd/(pwd+pwod)*100):0;let h='<div class="dashboard-kpi-row">';h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🚀</div><div class="dashboard-kpi-value">${total}</div><div class="dashboard-kpi-label">کل استقرارها</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">✅</div><div class="dashboard-kpi-value">${pwd}</div><div class="dashboard-kpi-label">با استقرار</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">❌</div><div class="dashboard-kpi-value">${pwod}</div><div class="dashboard-kpi-label">بدون استقرار</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">📈</div><div class="dashboard-kpi-value">${coverage}%</div><div class="dashboard-kpi-label">پوشش</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">📝</div><div class="dashboard-kpi-value">${withNotes}</div><div class="dashboard-kpi-label">با ریلیز نوت</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">⚠️</div><div class="dashboard-kpi-value">${withoutNotes}</div><div class="dashboard-kpi-label">بدون ریلیز نوت</div></div>`;h+='</div><div class="dashboard-charts-grid">';h+='<div class="dashboard-chart-card"><div class="dashboard-chart-header">🌍 استقرارها به تفکیک محیط</div><div class="dashboard-chart-body" id="deployEnvChart"></div></div>';h+='<div class="dashboard-chart-card"><div class="dashboard-chart-header">✅ وضعیت استقرار</div><div class="dashboard-chart-body" id="deployStatusChart"></div></div>';h+='<div class="dashboard-chart-card"><div class="dashboard-chart-header">⚠️ لیست ریسک‌ها</div><div class="dashboard-chart-body" id="deployRiskList" style="align-items:flex-start;overflow-y:auto;max-height:280px;width:100%;"></div></div>';h+='</div>';c.innerHTML=h;
// Env Bar Chart
const envEntries=Object.entries(envCounts);if(envEntries.length>0){const w=300,hh=200,m={top:10,right:10,bottom:40,left:30};const iw=w-m.left-m.right,ih=hh-m.top-m.bottom;const svg=d3.select('#deployEnvChart').append('svg').attr('width',w).attr('height',hh).append('g').attr('transform',`translate(${m.left},${m.top})`);const x=d3.scaleBand().domain(envEntries.map(d=>{const env=projectData.appData.environments.find(e=>e.id===d[0]);return env?env.name:d[0]})).range([0,iw]).padding(.3);const y=d3.scaleLinear().domain([0,d3.max(envEntries,d=>d[1])||1]).nice().range([ih,0]);svg.append('g').attr('transform',`translate(0,${ih})`).call(d3.axisBottom(x).tickSize(0)).selectAll('text').attr('fill','var(--text-muted)').attr('font-size','9px').attr('font-family','Vazirmatn');svg.append('g').call(d3.axisLeft(y).ticks(4).tickSize(-iw)).selectAll('text').attr('fill','var(--text-muted)').attr('font-size','9px');svg.selectAll('rect').data(envEntries).join('rect').attr('x',d=>x(projectData.appData.environments.find(e=>e.id===d[0])?.name||d[0])).attr('y',d=>y(d[1])).attr('width',x.bandwidth()).attr('height',d=>ih-y(d[1])).attr('fill',d=>{const env=projectData.appData.environments.find(e=>e.id===d[0]);return env?env.backgroundColor:'#38a169'}).attr('rx',4).style('cursor','pointer').on('mouseover',function(e,d){d3.select(this).attr('fill','#c9a961');const env=projectData.appData.environments.find(e=>e.id===d[0]);showD3Tooltip(e,`${env?env.name:d[0]}: ${d[1]} استقرار`)}).on('mousemove',e=>showD3Tooltip(e,`${d[1]}`)).on('mouseout',function(e,d){d3.select(this).attr('fill',projectData.appData.environments.find(e=>e.id===d[0])?.backgroundColor||'#38a169');hideD3Tooltip()});svg.selectAll('.bar-label').data(envEntries).join('text').attr('x',d=>x(projectData.appData.environments.find(e=>e.id===d[0])?.name||d[0])+x.bandwidth()/2).attr('y',d=>y(d[1])-5).attr('text-anchor','middle').attr('fill','var(--text)').attr('font-size','10px').attr('font-weight','700').text(d=>d[1])}else{document.getElementById('deployEnvChart').innerHTML='<div class="dashboard-empty-chart"><span>📊</span><div>هیچ استقراری وجود ندارد</div></div>'}
// Status Donut
if(pwd+pwod>0){const w=260,hh=200,r=Math.min(w,hh)/2-10;const svg=d3.select('#deployStatusChart').append('svg').attr('width',w).attr('height',hh).append('g').attr('transform',`translate(${w/2},${hh/2})`);const data=[{label:'با استقرار',value:pwd,color:'#38a169'},{label:'بدون استقرار',value:pwod,color:'#e53e3e'}];const pie=d3.pie().value(d=>d.value).sort(null);const arc=d3.arc().innerRadius(r*.6).outerRadius(r);const arcH=d3.arc().innerRadius(r*.6).outerRadius(r+6);const total=pwd+pwod;svg.selectAll('path').data(pie(data)).join('path').attr('d',arc).attr('fill',d=>d.data.color).attr('stroke','#fff').attr('stroke-width',2).style('cursor','pointer').on('mouseover',function(e,d){d3.select(this).transition().duration(150).attr('d',arcH(d));showD3Tooltip(e,`${d.data.label}: ${d.data.value} (${Math.round(d.data.value/total*100)}%)`)}).on('mousemove',e=>showD3Tooltip(e,`${d.data.label}: ${d.data.value}`)).on('mouseout',function(){d3.select(this).transition().duration(150).attr('d',arc);hideD3Tooltip()});const cov=Math.round(pwd/total*100);svg.append('text').attr('text-anchor','middle').attr('dy','-.2em').attr('fill',cov>=70?'#38a169':cov>=40?'#dd6b20':'#e53e3e').attr('font-size','24px').attr('font-weight','800').text(`${cov}%`);svg.append('text').attr('text-anchor','middle').attr('dy','1.2em').attr('fill','var(--text-muted)').attr('font-size','10px').text('پوشش')}
// Risk List
let riskHtml='';if(oldDeploys.length>0){oldDeploys.forEach(od=>{const env=projectData.appData.environments.find(e=>e.id===od.env);riskHtml+=`<div style="padding:10px 12px;background:rgba(221,107,32,.1);border-radius:6px;margin-bottom:8px;border-right:4px solid #dd6b20"><div style="font-weight:700;font-size:12px">⏰ استقرار قدیمی: ${od.project}</div><div style="font-size:11px;color:var(--text-muted)">v${od.version} در ${env?env.name:od.env} - ${od.daysOld} روز پیش</div></div>`})}if(withoutNotes>0){riskHtml+=`<div style="padding:10px 12px;background:rgba(229,62,62,.1);border-radius:6px;margin-bottom:8px;border-right:4px solid #e53e3e"><div style="font-weight:700;font-size:12px">📝 بدون ریلیز نوت: ${withoutNotes} مورد</div></div>`}if(!riskHtml){riskHtml='<div class="dashboard-empty-chart"><span>✅</span><div>هیچ ریسکی شناسایی نشد</div></div>'}document.getElementById('deployRiskList').innerHTML=riskHtml;
document.getElementById('deployAnalysisFooterStats').innerHTML=`<span>🚀 کل: <span class="stat-highlight">${total}</span></span><span>📈 پوشش: <span class="stat-highlight">${coverage}%</span></span><span>📝 با ریلیز نوت: <span class="stat-highlight">${withNotes}</span></span>`;document.getElementById('deployAnalysisRefresh').onclick=()=>this.render()}};
const VersionAnalysisManager={open(){ModalManager.open('versionAnalysisModal');setTimeout(()=>this.render(),150)},render(){const c=document.getElementById('versionAnalysisContainer');if(!c)return;let total=0;const unique=new Set();const majorCounts={};const oldVersions=[];const now=new Date();const thirtyDaysAgo=new Date(now.getTime()-30*24*60*60*1000);projectData.appData.products.forEach(prod=>{prod.projects.forEach(pr=>{(pr.deployments||[]).filter(d=>d.status==='active').forEach(d=>{total++;unique.add(d.version);const major=d.version.split('.')[0]||'0';const key=`v${major}.x`;majorCounts[key]=(majorCounts[key]||0)+1;if(d.deploymentDate&&new Date(d.deploymentDate)<thirtyDaysAgo){oldVersions.push({project:pr.title,version:d.version,daysOld:Math.floor((now-new Date(d.deploymentDate))/(1000*60*60*24))})}})})});let h='<div class="dashboard-kpi-row">';h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🔢</div><div class="dashboard-kpi-value">${total}</div><div class="dashboard-kpi-label">کل نسخه‌ها</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">📦</div><div class="dashboard-kpi-value">${unique.size}</div><div class="dashboard-kpi-label">یکتا</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">⏰</div><div class="dashboard-kpi-value">${oldVersions.length}</div><div class="dashboard-kpi-label">قدیمی (+۳۰ روز)</div></div>`;h+='</div><div class="dashboard-charts-grid">';h+='<div class="dashboard-chart-card"><div class="dashboard-chart-header">📊 توزیع نسخه‌ها بر اساس Major</div><div class="dashboard-chart-body" id="verMajorChart"></div></div>';h+='<div class="dashboard-chart-card"><div class="dashboard-chart-header">⚠️ لیست ریسک‌ها</div><div class="dashboard-chart-body" id="verRiskList" style="align-items:flex-start;overflow-y:auto;max-height:280px;width:100%;"></div></div>';h+='</div>';c.innerHTML=h;
// Major Bar Chart
const majorEntries=Object.entries(majorCounts);if(majorEntries.length>0){const w=300,hh=200,m={top:10,right:10,bottom:40,left:30};const iw=w-m.left-m.right,ih=hh-m.top-m.bottom;const svg=d3.select('#verMajorChart').append('svg').attr('width',w).attr('height',hh).append('g').attr('transform',`translate(${m.left},${m.top})`);const x=d3.scaleBand().domain(majorEntries.map(d=>d[0])).range([0,iw]).padding(.3);const y=d3.scaleLinear().domain([0,d3.max(majorEntries,d=>d[1])||1]).nice().range([ih,0]);svg.append('g').attr('transform',`translate(0,${ih})`).call(d3.axisBottom(x).tickSize(0)).selectAll('text').attr('fill','var(--text-muted)').attr('font-size','9px').attr('font-family','Vazirmatn');svg.append('g').call(d3.axisLeft(y).ticks(4).tickSize(-iw)).selectAll('text').attr('fill','var(--text-muted)').attr('font-size','9px');const colors=['#4299e1','#48bb78','#ecc94b','#ed64a6','#9f7aea','#ed8936'];svg.selectAll('rect').data(majorEntries).join('rect').attr('x',d=>x(d[0])).attr('y',d=>y(d[1])).attr('width',x.bandwidth()).attr('height',d=>ih-y(d[1])).attr('fill',(d,i)=>colors[i%colors.length]).attr('rx',4).style('cursor','pointer').on('mouseover',function(e,d){d3.select(this).attr('fill','#c9a961');showD3Tooltip(e,`${d[0]}: ${d[1]} نسخه`)}).on('mousemove',e=>showD3Tooltip(e,`${d[1]}`)).on('mouseout',function(e,d){d3.select(this).attr('fill',colors[majorEntries.indexOf(d)%colors.length]);hideD3Tooltip()});svg.selectAll('.bar-label').data(majorEntries).join('text').attr('x',d=>x(d[0])+x.bandwidth()/2).attr('y',d=>y(d[1])-5).attr('text-anchor','middle').attr('fill','var(--text)').attr('font-size','10px').attr('font-weight','700').text(d=>d[1])}else{document.getElementById('verMajorChart').innerHTML='<div class="dashboard-empty-chart"><span>📊</span><div>هیچ نسخه‌ای وجود ندارد</div></div>'}
// Risk List
let riskHtml='';if(oldVersions.length>0){oldVersions.forEach(ov=>{riskHtml+=`<div style="padding:10px 12px;background:rgba(221,107,32,.1);border-radius:6px;margin-bottom:8px;border-right:4px solid #dd6b20"><div style="font-weight:700;font-size:12px">⏰ نسخه قدیمی: ${ov.project}</div><div style="font-size:11px;color:var(--text-muted)">v${ov.version} - ${ov.daysOld} روز پیش</div></div>`})}if(!riskHtml){riskHtml='<div class="dashboard-empty-chart"><span>✅</span><div>هیچ ریسکی شناسایی نشد</div></div>'}document.getElementById('verRiskList').innerHTML=riskHtml;
document.getElementById('verAnalysisFooterStats').innerHTML=`<span>🔢 کل: <span class="stat-highlight">${total}</span></span><span>📦 یکتا: <span class="stat-highlight">${unique.size}</span></span><span>⏰ قدیمی: <span class="stat-highlight">${oldVersions.length}</span></span>`;document.getElementById('verAnalysisRefresh').onclick=()=>this.render()}};
const RiskAnalysisManager={open(){ModalManager.open('riskAnalysisModal');setTimeout(()=>this.render(),150)},render(){const c=document.getElementById('riskAnalysisContainer');if(!c)return;const risks=[];projectData.appData.products.forEach(prod=>{prod.projects.forEach(pr=>{(pr.dependencies||[]).forEach(depId=>{if(!ProductManager.findProjectById(depId))risks.push({type:'broken',severity:'critical',title:'وابستگی شکسته',desc:`${pr.title} → پروژه ناموجود`})});if((pr.deployments||[]).filter(d=>d.status==='active').length===0)risks.push({type:'no-deploy',severity:'high',title:'بدون استقرار',desc:pr.title});if(!prod.metadata?.owner)risks.push({type:'no-owner',severity:'medium',title:'بدون مالک',desc:prod.title});(pr.deployments||[]).filter(d=>d.status==='active').forEach(d=>{if(!d.releaseNotes)risks.push({type:'no-notes',severity:'medium',title:'بدون ریلیز نوت',desc:`${pr.title} v${d.version}`})})})});const crit=risks.filter(r=>r.severity==='critical').length;const high=risks.filter(r=>r.severity==='high').length;const med=risks.filter(r=>r.severity==='medium').length;let healthScore=Math.max(0,100-crit*15-high*10-med*5);let h='<div class="dashboard-kpi-row">';h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🎯</div><div class="dashboard-kpi-value">${healthScore}%</div><div class="dashboard-kpi-label">امتیاز سلامت</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🔴</div><div class="dashboard-kpi-value">${crit}</div><div class="dashboard-kpi-label">بحرانی</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🟠</div><div class="dashboard-kpi-value">${high}</div><div class="dashboard-kpi-label">بالا</div></div>`;h+=`<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">🟡</div><div class="dashboard-kpi-value">${med}</div><div class="dashboard-kpi-label">متوسط</div></div>`;h+='</div><div class="dashboard-charts-grid">';h+='<div class="dashboard-chart-card"><div class="dashboard-chart-header">🍩 توزیع شدت ریسک‌ها</div><div class="dashboard-chart-body" id="riskSeverityChart"></div></div>';h+='<div class="dashboard-chart-card"><div class="dashboard-chart-header">📋 لیست ریسک‌ها</div><div class="dashboard-chart-body" id="riskDetailList" style="align-items:flex-start;overflow-y:auto;max-height:280px;width:100%;"></div></div>';h+='</div>';c.innerHTML=h;
// Severity Donut
const severityData=[{label:'بحرانی',value:crit,color:'#e53e3e'},{label:'بالا',value:high,color:'#dd6b20'},{label:'متوسط',value:med,color:'#d69e2e'}].filter(d=>d.value>0);if(severityData.length>0){const w=260,hh=200,r=Math.min(w,hh)/2-10;const svg=d3.select('#riskSeverityChart').append('svg').attr('width',w).attr('height',hh).append('g').attr('transform',`translate(${w/2},${hh/2})`);const pie=d3.pie().value(d=>d.value).sort(null);const arc=d3.arc().innerRadius(r*.55).outerRadius(r);const arcH=d3.arc().innerRadius(r*.55).outerRadius(r+6);const total=severityData.reduce((s,d)=>s+d.value,0);svg.selectAll('path').data(pie(severityData)).join('path').attr('d',arc).attr('fill',d=>d.data.color).attr('stroke','#fff').attr('stroke-width',2).style('cursor','pointer').on('mouseover',function(e,d){d3.select(this).transition().duration(150).attr('d',arcH(d));showD3Tooltip(e,`${d.data.label}: ${d.data.value} (${Math.round(d.data.value/total*100)}%)`)}).on('mousemove',e=>showD3Tooltip(e,`${d.data.label}: ${d.data.value}`)).on('mouseout',function(){d3.select(this).transition().duration(150).attr('d',arc);hideD3Tooltip()});svg.append('text').attr('text-anchor','middle').attr('dy','-.2em').attr('fill',healthScore>=70?'#38a169':healthScore>=40?'#dd6b20':'#e53e3e').attr('font-size','24px').attr('font-weight','800').text(`${healthScore}%`);svg.append('text').attr('text-anchor','middle').attr('dy','1.2em').attr('fill','var(--text-muted)').attr('font-size','10px').text('سلامت')}else{document.getElementById('riskSeverityChart').innerHTML='<div class="dashboard-empty-chart"><span>✅</span><div>هیچ ریسکی شناسایی نشد</div></div>'}
// Risk List
let riskHtml='';if(risks.length>0){risks.forEach(r=>{const colors={critical:'#e53e3e',high:'#dd6b20',medium:'#d69e2e'};riskHtml+=`<div style="padding:10px 12px;background:rgba(0,0,0,.05);border-radius:6px;margin-bottom:8px;border-right:4px solid ${colors[r.severity]||'#3182ce'}"><div style="font-weight:700;font-size:12px">${r.title}</div><div style="font-size:11px;color:var(--text-muted)">${r.desc}</div></div>`})}else{riskHtml='<div class="dashboard-empty-chart"><span>✅</span><div>هیچ ریسکی شناسایی نشد</div></div>'}document.getElementById('riskDetailList').innerHTML=riskHtml;
document.getElementById('riskAnalysisFooterStats').innerHTML=`<span>🎯 سلامت: <span class="stat-highlight">${healthScore}%</span></span><span>⚠️ ریسک‌ها: <span class="stat-highlight">${risks.length}</span></span>`;document.getElementById('riskAnalysisRefresh').onclick=()=>this.render()}};
/* ==================== EVENT BINDINGS ==================== */
document.getElementById('projectDetailDeleteBtn').addEventListener('click',()=>{if(ProductManager.currentDetailProductId)ProductManager.removeProduct(ProductManager.currentDetailProductId)});
document.getElementById('saveProductEditBtn').addEventListener('click',()=>ProductManager.saveProductEdit());
document.getElementById('saveNewProjectBtn').addEventListener('click',()=>ProductManager.saveNewProject());
document.getElementById('saveProjEditBtn').addEventListener('click',()=>ProductManager.saveProjectEdit());
document.getElementById('saveDeploymentBtn').addEventListener('click',()=>ProductManager.saveDeployment());
document.getElementById('saveEnvEditBtn').addEventListener('click',()=>EnvironmentManager.saveEdit());
document.getElementById('saveNewProductBtn').addEventListener('click',()=>ProductManager.createNewProduct());
document.getElementById('saveNewEnvBtn').addEventListener('click',()=>EnvironmentManager.saveNewEnv());
document.getElementById('newEnvBgColor').addEventListener('input',e=>{document.getElementById('newEnvBgColorText').textContent=e.target.value});
document.getElementById('newEnvTextColor').addEventListener('input',e=>{document.getElementById('newEnvTextColorText').textContent=e.target.value});
document.getElementById('editEnvBgColor').addEventListener('input',e=>{document.getElementById('editEnvBgColorText').textContent=e.target.value;EnvironmentManager.updateEditPreview()});
document.getElementById('editEnvTextColor').addEventListener('input',e=>{document.getElementById('editEnvTextColorText').textContent=e.target.value;EnvironmentManager.updateEditPreview()});
document.getElementById('galaxyResetView').addEventListener('click',()=>{if(GalaxyChartManager.svg&&GalaxyChartManager.zoomBehavior)GalaxyChartManager.svg.transition().duration(500).call(GalaxyChartManager.zoomBehavior.transform,d3.zoomIdentity)});
document.getElementById('galaxyInfoClose').addEventListener('click',()=>{document.getElementById('galaxyInfoPanel').classList.remove('visible')});
document.getElementById('galaxyExportPNG').addEventListener('click',()=>{showToast('📷 خروجی ذخیره شد','success')});
function handleToolSelect(tool,group){const ah=ActionRegistry[tool.action]||ActionRegistry.defaultAction;ah(tool,group);WebhookManager.sendEvent('tool_selected',{toolId:tool.id,groupId:group.id})}
const themeToggle=document.getElementById('themeToggle'),themeIcon=document.getElementById('themeIcon'),htmlEl=document.documentElement;
const savedTheme=localStorage.getItem('theme')||'light';
htmlEl.setAttribute('data-theme',savedTheme);
if(themeIcon)themeIcon.textContent=savedTheme==='dark'?'☀️':'🌙';
if(themeToggle){themeToggle.addEventListener('click',()=>{const c=htmlEl.getAttribute('data-theme');const n=c==='dark'?'light':'dark';htmlEl.setAttribute('data-theme',n);localStorage.setItem('theme',n);if(themeIcon)themeIcon.textContent=n==='dark'?'☀️':'🌙'})}
const sidebarToggle=document.getElementById('sidebarToggle'),sidebar=document.getElementById('sidebar'),sidebarIcon=document.getElementById('sidebarIcon');
function updateSidebarIcon(){if(!sidebarIcon)return;sidebarIcon.textContent=sidebar.classList.contains('collapsed')?'☰':'✕'}
if(sidebarToggle){sidebarToggle.addEventListener('click',()=>{sidebar.classList.toggle('collapsed');updateSidebarIcon()})}
updateSidebarIcon();
const sidebarBody=document.getElementById('sidebarBody');
function renderToolGroups(){sidebarBody.innerHTML='';projectData.toolGroups.forEach(group=>{const ge=document.createElement('div');ge.className='sidebar-group';const he=document.createElement('div');he.className='sidebar-group-header';he.innerHTML=`<div class="group-icon"><span>${group.icon||''}</span><span>${group.title}</span></div><span class="group-arrow">▼</span>`;const be=document.createElement('div');be.className='sidebar-group-body';group.tools.forEach(tool=>{const btn=document.createElement('button');btn.className='sidebar-btn';btn.title=tool.description;btn.innerHTML=`<span style="width:20px;text-align:center">${tool.icon||'🔧'}</span><span style="flex:1">${tool.title}</span>`;btn.addEventListener('click',()=>handleToolSelect(tool,group));be.appendChild(btn)});he.addEventListener('click',()=>ge.classList.toggle('collapsed'));ge.appendChild(he);ge.appendChild(be);sidebarBody.appendChild(ge)})}
function openSettingsModal(){document.getElementById('settingProjectTitle').value=projectData.projectTitle;document.getElementById('settingVersion').value=projectData.version;document.getElementById('settingDescription').value=projectData.description;document.getElementById('settingDeveloper').value=projectData.developer;document.getElementById('settingCompany').value=projectData.company;document.getElementById('settingWebhookUrl').value=projectData.webhook.url;document.getElementById('settingWebhookClientId').value=projectData.webhook.clientId;document.getElementById('settingWebhookEnabled').checked=projectData.webhook.enabled;WebhookManager.updateStatusUI();ModalManager.open('settingsModal')}
['settingWebhookUrl','settingWebhookClientId','settingWebhookEnabled'].forEach(id=>{document.getElementById(id).addEventListener('input',()=>{WebhookManager.setConfig({url:document.getElementById('settingWebhookUrl').value,clientId:document.getElementById('settingWebhookClientId').value,enabled:document.getElementById('settingWebhookEnabled').checked})})});
document.getElementById('saveSettingsBtn').addEventListener('click',()=>{WebhookManager.setConfig({url:document.getElementById('settingWebhookUrl').value.trim(),clientId:document.getElementById('settingWebhookClientId').value.trim(),enabled:document.getElementById('settingWebhookEnabled').checked});ModalManager.close('settingsModal');showToast('تنظیمات ذخیره شد','success')});
function updateProjectUI(){document.title=projectData.projectTitle;document.getElementById('headerTitle').textContent=projectData.projectTitle;document.getElementById('headerSubtitle').textContent=projectData.description;document.getElementById('appFooter').innerHTML=`<div><span class="footer-brand">طراح و توسعه‌دهنده:</span> ${projectData.developer} <span style="margin:0 10px">|</span> <span class="footer-brand">شرکت:</span> ${projectData.company}</div><div><span class="footer-brand">نسخه:</span> ${projectData.version}</div>`}
/* ==================== INIT ==================== */
ModalManager.init();renderToolGroups();updateProjectUI();WebhookManager.updateStatusUI();ToolbarManager.init();SectionCollapseManager.init();BoardRenderer.render();HistoryManager.init();
setTimeout(()=>showToast(`${projectData.projectTitle} (نسخه ${projectData.version}) آماده است`,'success'),500);
</script>
</body>
</html>