353 lines
98 KiB
HTML
353 lines
98 KiB
HTML
<!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>
|
||
<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;--sidebar-width:280px;--header-height:64px;--tabs-height:48px;--footer-height:36px}
|
||
[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:var(--header-height);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;position:relative;overflow:hidden}
|
||
.app-header::before{content:'';position:absolute;top:-50%;left:-10%;width:300px;height:300px;background:radial-gradient(circle,rgba(201,169,97,.15) 0%,transparent 70%);border-radius:50%;pointer-events:none}
|
||
.app-brand{display:flex;align-items:center;gap:14px;position:relative;z-index:1}
|
||
.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);box-shadow:0 4px 12px rgba(0,0,0,.25);flex-shrink:0}
|
||
.app-brand-text{display:flex;flex-direction:column;gap:2px}
|
||
.app-title{font-size:16px;font-weight:800;letter-spacing:-.3px;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;box-shadow:0 2px 4px rgba(0,0,0,.15);white-space:nowrap}
|
||
.btn:hover{transform:translateY(-1px);box-shadow:0 4px 8px rgba(0,0,0,.2);filter:brightness(1.1)}
|
||
.btn:active{transform:translateY(0)}
|
||
.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);font-weight:600}
|
||
.btn-ghost:hover{background:rgba(26,54,93,.15);border-color:var(--primary-light)}
|
||
[data-theme="dark"] .btn-ghost{background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.25);color:#fff}
|
||
[data-theme="dark"] .btn-ghost:hover{background:rgba(255,255,255,.2)}
|
||
.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}
|
||
.btn-icon{width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;font-size:14px}
|
||
.app-main{flex:1;display:flex;overflow:hidden;position:relative;flex-direction:row}
|
||
.sidebar{order:-1;width:var(--sidebar-width);background:var(--panel-bg);border-right:1px solid var(--border);display:flex;flex-direction:column;transition:width .3s ease;box-shadow:var(--shadow);overflow:hidden;flex-shrink:0;z-index:50}
|
||
.sidebar.collapsed{width:0;overflow:hidden}
|
||
.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;white-space:nowrap;overflow:hidden}
|
||
.sidebar.collapsed .sidebar-title{display:none}
|
||
.sidebar-body{flex:1;overflow-y:auto;padding:8px}
|
||
.sidebar.collapsed .sidebar-body{display:none}
|
||
.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;transition:all .2s;position:relative}
|
||
.sidebar-group-header:hover{filter:brightness(1.1)}
|
||
.sidebar-group-header .group-icon{display:flex;align-items:center;gap:8px;flex:1;overflow:hidden}
|
||
.sidebar-group-header .group-arrow{transition:transform .3s;font-size:12px;flex-shrink:0}
|
||
.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;transition:all .2s;text-align:right;width:100%;position:relative}
|
||
.sidebar-btn:hover{background:linear-gradient(135deg,var(--primary),var(--primary-light));color:#fff;border-color:var(--primary);transform:translateX(-2px)}
|
||
.sidebar-btn .btn-icon-emoji{font-size:15px;width:20px;text-align:center;flex-shrink:0}
|
||
.sidebar-btn .btn-title{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||
[data-tooltip]{position:relative}
|
||
[data-tooltip]::after{content:attr(data-tooltip);position:absolute;right:calc(100% + 10px);top:50%;transform:translateY(-50%);background:var(--primary-dark);color:#fff;padding:8px 12px;border-radius:6px;font-size:11px;font-weight:500;white-space:normal;opacity:0;pointer-events:none;transition:opacity .2s;z-index:1000;box-shadow:0 4px 12px rgba(0,0,0,.3);max-width:250px;line-height:1.5}
|
||
[data-tooltip]::before{content:'';position:absolute;right:calc(100% + 5px);top:50%;transform:translateY(-50%);border:5px solid transparent;border-left-color:var(--primary-dark);opacity:0;pointer-events:none;transition:opacity .2s;z-index:1000}
|
||
[data-tooltip]:hover::after,[data-tooltip]:hover::before{opacity:1}
|
||
[data-theme="dark"] [data-tooltip]::after{background:var(--accent-dark);color:var(--primary-dark)}
|
||
[data-theme="dark"] [data-tooltip]::before{border-left-color:var(--accent-dark)}
|
||
.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:var(--tabs-height);box-shadow:var(--shadow-sm);flex-shrink:0;gap:10px;position:relative;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;transition:all .2s;padding:0}
|
||
.toolbar-icon-btn:hover:not(:disabled){background:var(--accent);color:var(--primary-dark);border-color:var(--accent);transform:translateY(-1px)}
|
||
.toolbar-icon-btn svg{display:block}
|
||
.toolbar-search{flex:1;min-width:180px;max-width:320px;position:relative;display:flex;align-items:center}
|
||
.search-icon{position:absolute;right:10px;font-size:13px;opacity:.7;pointer-events:none}
|
||
.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;transition:all .2s}
|
||
.search-input::placeholder{color:rgba(255,255,255,.5)}
|
||
.search-input:focus{outline:none;background:rgba(255,255,255,.12);border-color:var(--accent);box-shadow:0 0 0 2px rgba(201,169,97,.2)}
|
||
.toolbar-filters{display:flex;gap:8px}
|
||
.filter-dropdown{position:relative;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;transition:all .2s}
|
||
.filter-dropdown:hover{border-color:rgba(255,255,255,.3);background:rgba(255,255,255,.12)}
|
||
.filter-icon{font-size:13px;opacity:.9}
|
||
.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);white-space:nowrap}
|
||
.stat-icon{font-size:13px}
|
||
.stat-label{font-weight:500}
|
||
.stat-value{font-weight:800;color:var(--accent);background:rgba(201,169,97,.15);padding:2px 7px;border-radius:10px;font-size:11px;min-width:22px;text-align:center}
|
||
@media(max-width:900px){.tabs-bar{flex-wrap:wrap;padding:8px;gap:8px}.toolbar-stats{order:3;width:100%;margin-right:0;padding-right:0;border-right:none;border-top:1px solid rgba(255,255,255,.1);padding-top:8px;justify-content:center}.toolbar-search{order:2}}
|
||
.board-area{flex:1;overflow:auto;padding:24px;background:var(--board-bg);transition:background .3s}
|
||
.board-section{margin-bottom:32px}
|
||
.board-section:last-child{margin-bottom:0}
|
||
.board-section.collapsed .section-content{display:none}
|
||
.board-section.collapsed .collapse-icon{transform:rotate(90deg)}
|
||
.collapse-icon{transition:transform .3s ease;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;position:relative}
|
||
.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;position:relative;overflow:hidden}
|
||
.project-card-header::before{content:'';position:absolute;top:-30px;left:-30px;width:100px;height:100px;background:radial-gradient(circle,rgba(201,169,97,.2) 0%,transparent 70%);border-radius:50%}
|
||
.project-card-title-row{display:flex;align-items:center;gap:10px;margin-bottom:4px;position:relative;z-index:1}
|
||
.project-card-icon{font-size:22px;flex-shrink:0}
|
||
.project-card-title{font-size:16px;font-weight:700;flex:1}
|
||
.project-card-desc{font-size:12px;opacity:.9;position:relative;z-index:1;line-height:1.5;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
|
||
.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}
|
||
[data-theme="dark"] .type-backend{background:rgba(66,153,225,.2);color:#90cdf4}[data-theme="dark"] .type-frontend{background:rgba(72,187,120,.2);color:#9ae6b4}[data-theme="dark"] .type-service{background:rgba(236,201,75,.2);color:#fefcbf}[data-theme="dark"] .type-database{background:rgba(237,100,166,.2);color:#fbb6ce}[data-theme="dark"] .type-mobile{background:rgba(159,122,234,.2);color:#d6bcfa}[data-theme="dark"] .type-infrastructure{background:rgba(237,137,54,.2);color:#fbd38d}
|
||
.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);transition:all .25s;display:flex;flex-direction:column}
|
||
.env-card-item:hover{transform:translateY(-3px);box-shadow:var(--shadow-lg);border-color:var(--accent)}
|
||
.env-card-preview{height:60px;display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:700;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.2)}
|
||
.env-card-body{padding:14px 16px;flex:1;display:flex;flex-direction:column;gap:8px}
|
||
.env-card-name{font-size:15px;font-weight:700;color:var(--text)}
|
||
.env-card-desc{font-size:12px;color:var(--text-muted);line-height:1.5}
|
||
.env-card-footer{padding:10px 16px;background:var(--border-light);border-top:1px solid var(--border);display:flex;justify-content:flex-start;gap:6px}
|
||
.board-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;color:var(--text-muted);text-align:center}
|
||
.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)}
|
||
.board-empty p{margin-bottom:20px}
|
||
.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;animation:fadeIn .2s}
|
||
.modal-overlay.active{display:flex}
|
||
@keyframes fadeIn{from{opacity:0}to{opacity:1}}
|
||
.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);animation:slideUp .25s}
|
||
.modal-lg{max-width:800px}.modal-xl{max-width:1000px}
|
||
@keyframes slideUp{from{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}
|
||
.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;transition:all .2s}
|
||
.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}
|
||
#projectDetailModal .modal-footer{justify-content:flex-start}
|
||
.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;color:var(--text)}
|
||
.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);transition:all .2s}
|
||
.subproject-card:hover{border-color:var(--accent);box-shadow:var(--shadow-sm)}
|
||
.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:6px 10px;background:var(--card-bg);border:1px solid var(--border-light);border-radius:6px;font-size:11px;transition:all .2s}
|
||
.deployment-item:hover{border-color:var(--accent);box-shadow:var(--shadow-sm)}
|
||
.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}
|
||
.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);transition:all .2s}
|
||
.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-input.error{border-color:var(--danger);box-shadow:0 0 0 3px rgba(229,62,62,.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}
|
||
[data-theme="dark"] .webhook-section-title{color:var(--accent-light)}
|
||
.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);flex-shrink:0}
|
||
.webhook-status-dot.success{background:var(--success)}
|
||
.webhook-status-dot.error{background:var(--danger)}
|
||
.webhook-status-dot.pending{background:var(--warning);animation:pulse 1.5s infinite}
|
||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
||
.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;animation:slideInLeft .3s;min-width:250px}
|
||
.toast.success{background:var(--success)}.toast.error{background:var(--danger)}.toast.warning{background:var(--warning)}
|
||
@keyframes slideInLeft{from{opacity:0;transform:translateX(-30px)}to{opacity:1;transform:translateX(0)}}
|
||
.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}
|
||
[data-theme="dark"] .readonly-info{background:rgba(49,130,206,.12);border-color:rgba(49,130,206,.3);border-right-color:var(--info)}
|
||
.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);transition:all .2s}
|
||
.dependency-item:hover{border-color:var(--accent);box-shadow:var(--shadow-sm)}
|
||
.dependency-info{display:flex;align-items:center;gap:10px;flex:1}
|
||
.dependency-project-name{font-weight:600;font-size:13px}
|
||
.dependency-version-info{font-size:11px;color:var(--text-muted)}
|
||
.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-input:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(201,169,97,.15)}
|
||
.dependency-search-results{max-height:300px;overflow-y:auto}
|
||
.dependency-project-result{margin-bottom:12px;border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;transition:all .2s}
|
||
.dependency-project-result:hover{border-color:var(--accent);box-shadow:var(--shadow-sm)}
|
||
.dependency-project-header{background:var(--border-light);padding:10px 12px;font-weight:600;font-size:13px;cursor:pointer;display:flex;justify-content:space-between;align-items:center;transition:all .2s}
|
||
.dependency-project-header:hover{background:var(--accent-light)}
|
||
.dependency-versions-list{padding:8px;display:flex;flex-direction:column;gap:4px}
|
||
.dependency-version-item{padding:8px 10px;background:var(--card-bg);border:1px solid var(--border-light);border-radius:4px;cursor:pointer;display:flex;justify-content:space-between;align-items:center;font-size:12px;transition:all .2s}
|
||
.dependency-version-item:hover{background:var(--accent-light);border-color:var(--accent)}
|
||
.dependency-version-badge{font-size:10px;padding:2px 6px;border-radius:4px;background:var(--info);color:#fff}
|
||
.dependency-version-badge.history{background:var(--text-muted)}
|
||
.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)}
|
||
::-webkit-scrollbar{width:8px;height:8px}
|
||
::-webkit-scrollbar-track{background:transparent}
|
||
::-webkit-scrollbar-thumb{background:var(--border);border-radius:4px}
|
||
::-webkit-scrollbar-thumb:hover{background:var(--text-muted)}
|
||
.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}
|
||
.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;transition:all .25s;box-shadow:var(--shadow-sm);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-card:hover{transform:translateY(-3px);box-shadow:var(--shadow-lg);border-color: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}
|
||
</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></header>
|
||
<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><svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62c1.39-1.16 3.16-1.88 5.12-1.88 3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z"/></svg></button><button class="toolbar-icon-btn" id="redoBtn" title="باز انجام (Ctrl+Y)" disabled><svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M18.4 10.6C16.55 9 14.15 8 11.5 8c-4.65 0-8.58 3.03-9.96 7.22L3.9 16c1.05-3.19 4.05-5.5 7.6-5.5 1.95 0 3.73.72 5.12 1.88L13 16h9V7l-3.6 3.6z"/></svg></button></div><div class="toolbar-search"><span class="search-icon">🔍</span><input type="text" class="search-input" id="toolbarSearch" placeholder="جستجو..."></div><div class="toolbar-filters"><div class="filter-dropdown"><span class="filter-icon">🏷️</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 class="filter-icon">🌐</span><select class="filter-select" id="filterEnvironment"><option value="">همه محیطها</option></select></div></div><div class="toolbar-stats"><div class="stat-item"><span class="stat-icon">📋</span><span class="stat-label">کل:</span><span class="stat-value" id="statTotal">0</span></div><div class="stat-item"><span class="stat-icon">📦</span><span class="stat-label">محصول:</span><span class="stat-value" id="statProjects">0</span></div><div class="stat-item"><span class="stat-icon">🌿</span><span class="stat-label">پروژه:</span><span class="stat-value" id="statSubProjects">0</span></div><div class="stat-item"><span class="stat-icon">🔗</span><span class="stat-label">وابستگی:</span><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"><span>ℹ️</span><span>اطلاعات پایه محصول فقط قابل مشاهده هستند.</span></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"><span>🔗</span><span>تنظیمات وبهوک</span></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" placeholder="https://example.com/webhook"></div><div class="form-group"><label class="form-label">شناسه کلاینت (Client ID) <span class="required">*</span></label><input type="text" class="form-input" id="settingWebhookClientId" dir="ltr" placeholder="client-xxxx-xxxx"></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 Manager Modal -->
|
||
<div class="modal-overlay" id="envManagerModal"><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="form-row"><div class="form-group"><label class="form-label">نام محیط <span class="required">*</span></label><input type="text" class="form-input" id="newEnvName" placeholder="مثلاً: QA"></div><div class="form-group"><label class="form-label">توضیحات</label><input type="text" class="form-input" id="newEnvDesc" placeholder="توضیحات محیط"></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>
|
||
|
||
<!-- Environment Edit Modal -->
|
||
<div class="modal-overlay" id="envEditModal"><div class="modal"><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="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"><span>📦</span><span>جزئیات محصول</span></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>
|
||
|
||
<!-- Product Edit Modal -->
|
||
<div class="modal-overlay" id="productEditModal"><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="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" rows="3"></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>
|
||
|
||
<!-- Add Product Modal -->
|
||
<div class="modal-overlay" id="addProductModal"><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"><span>ℹ️</span><span>فیلدهای دارای <span style="color:var(--danger)">*</span> الزامی هستند.</span></div><div class="form-group"><label class="form-label">عنوان محصول <span class="required">*</span></label><input type="text" class="form-input" id="newProductTitle" placeholder="مثلاً: سامانه فروشگاه آنلاین"><div class="form-error" id="newProductTitleError">عنوان محصول الزامی است و باید حداقل ۳ کاراکتر باشد</div></div><div class="form-group"><label class="form-label">توضیحات</label><textarea class="form-textarea" id="newProductDescription" rows="3" placeholder="توضیحات مختصر..."></textarea></div><div class="form-row"><div class="form-group"><label class="form-label">مالک / تیم مسئول</label><input type="text" class="form-input" id="newProductOwner" placeholder="مثلاً: تیم توسعه محصول"></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>
|
||
|
||
<!-- Add Project Modal -->
|
||
<div class="modal-overlay" id="addProjectModal"><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="form-group"><label class="form-label">عنوان پروژه <span class="required">*</span></label><input type="text" class="form-input" id="newProjectTitle" placeholder="مثلاً: Backend API Service"></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" placeholder="مثلاً: .NET 8"></div></div><div class="form-row"><div class="form-group"><label class="form-label">زبان برنامهنویسی</label><input type="text" class="form-input" id="newProjectLanguage" placeholder="مثلاً: C#"></div><div class="form-group"><label class="form-label">آدرس مخزن</label><input type="text" class="form-input" id="newProjectRepo" dir="ltr" placeholder="https://github.com/..."></div></div><div class="form-group"><label class="form-label">توضیحات</label><textarea class="form-textarea" id="newProjectDescription" rows="3" placeholder="توضیحات پروژه..."></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>
|
||
|
||
<!-- Edit Project Modal -->
|
||
<div class="modal-overlay" id="editProjectModal"><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="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" rows="3"></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"><div class="modal-header"><div class="modal-title" id="deploymentModalTitle"><span>🚀</span><span>افزودن نسخه استقرار</span></div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body"><div class="form-group" id="deploymentEnvGroup"></div><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="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"><span>🔗</span><span>مدیریت وابستگیها</span></div><button class="modal-close" data-close-modal>✕</button></div><div class="modal-body"><div class="readonly-info"><span>ℹ️</span><span>وابستگیهای پروژه فعلی را مدیریت کنید.</span></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>
|
||
|
||
<div class="toast-container" id="toastContainer"></div>
|
||
|
||
<script>
|
||
const projectData={projectId:'550e8400-e29b-41d4-a716-446655440000',projectName:'xDependencies',projectTitle:'مدیریت محصولات',description:'مدیریت نسخهها و نیازمندیهای محصولات',version:'1.4.20',developer:'هادی خزاعی اصل',company:'رایان پرداز کاوش',metadata:{createdAt:'2026-09-13T00:00:00Z',updatedAt:'2026-09-14T00:00:00Z',tags:['release-management','architecture','product-management'],customFields:{}},webhook:{url:'',clientId:'',enabled:false},toolGroups:[{id:'product-management',title:'پرونده',icon:'📁',description:'مدیریت چرخه حیات محصول',tools:[{id:'clear-all',title:'پاکسازی',icon:'🗑️',action:'clearAll',shortcut:null,description:'پاکسازی کامل همه موارد'},{id:'save-product',title:'ذخیره',icon:'💾',action:'saveProduct',shortcut:null,description:'دانلود دادههای محصول'},{id:'load-product',title:'بارگذاری',icon:'📂',action:'loadProduct',shortcut:null,description:'بارگذاری از فایل JSON'},{id:'product-settings',title:'تنظیمات',icon:'⚙️',action:'openProductSettings',shortcut:null,description:'پیکربندی تنظیمات'}]},{id:'reports',title:'گزارشها',icon:'📊',description:'تحلیل و گزارشگیری',tools:[{id:'view-reports',title:'نمایش گزارشها',icon:'📈',action:'viewReports',shortcut:null,description:'آمار کلی محصول'},{id:'dependency-graph',title:'نمودار وابستگی',icon:'🕸️',action:'showDependencyGraph',shortcut:null,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'}],products:[{id:'prod-1001',title:'سامانه فروشگاه آنلاین',description:'پلتفرم جامع فروش محصولات',metadata:{createdAt:'2026-09-01T00:00:00Z',owner:'تیم توسعه محصول',customFields:{}},projects:[{id:'proj-1001-01',title:'Backend API Service',description:'سرویس بکاند اصلی',type:'backend',repositoryUrl:'',metadata:{framework:'.NET 8',language:'C#'},dependencies:[],lastDeployedVersions:[{environmentId:'env-prod-001',version:'2.3.1',deploymentDate:'2026-09-10T14:30:00Z'},{environmentId:'env-stage-002',version:'2.4.0-rc.1',deploymentDate:'2026-09-12T09:00:00Z'}],deploymentHistory:[]},{id:'proj-1001-02',title:'Frontend Web Application',description:'اپلیکیشن فرانتاند',type:'frontend',repositoryUrl:'',metadata:{framework:'Angular 18',language:'TypeScript'},dependencies:['proj-1001-01'],lastDeployedVersions:[{environmentId:'env-prod-001',version:'3.1.0',deploymentDate:'2026-09-11T10:00:00Z'}],deploymentHistory:[]},{id:'proj-1001-03',title:'Payment Gateway Service',description:'سرویس درگاه پرداخت',type:'service',repositoryUrl:'',metadata:{framework:'.NET 8',language:'C#'},dependencies:['proj-1001-01'],lastDeployedVersions:[{environmentId:'env-prod-001',version:'1.2.0',deploymentDate:'2026-09-08T16:00:00Z'}],deploymentHistory:[]}]}]}};
|
||
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.transition='opacity .3s,transform .3s';t.style.opacity='0';t.style.transform='translateX(-30px)';setTimeout(()=>t.remove(),300)},dur)}
|
||
|
||
/* ========== تابع مرکزی بهروزرسانی - رفع باگ ========== */
|
||
function refreshUI(){
|
||
ToolbarManager.updateStats();
|
||
ToolbarManager.populateEnvironmentFilter();
|
||
BoardRenderer.render();
|
||
if(ProductManager.currentDetailProductId){
|
||
const prod=projectData.appData.products.find(p=>p.id===ProductManager.currentDetailProductId);
|
||
if(prod){
|
||
if(document.getElementById('projectDetailModal').classList.contains('active')){
|
||
ProductManager.showDetail(ProductManager.currentDetailProductId);
|
||
}
|
||
}else{
|
||
ProductManager.currentDetailProductId=null;
|
||
ModalManager.close('projectDetailModal');
|
||
}
|
||
}
|
||
}
|
||
|
||
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()}})}};
|
||
const ToolbarManager={currentFilters:{query:'',typeFilter:'',envFilter:''},init(){this.populateEnvironmentFilter();this.bindEvents();this.updateStats()},populateEnvironmentFilter(){const s=document.getElementById('filterEnvironment');if(!s)return;const envs=projectData.appData.environments||[];s.innerHTML='<option value="">همه محیطها</option>'+envs.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();BoardRenderer.render()});if(ft)ft.addEventListener('change',()=>{this.applyFilters();BoardRenderer.render()});if(fe)fe.addEventListener('change',()=>{this.applyFilters();BoardRenderer.render()})},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)||(pr.description||'').toLowerCase().includes(f.query);const mt=!f.typeFilter||pr.type===f.typeFilter;const me=!f.envFilter||(pr.lastDeployedVersions||[]).some(d=>d.environmentId===f.envFilter);return mq&&mt&&me})}};
|
||
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;refreshUI();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;refreshUI();this.updateButtons();showToast('↪️ باز انجام تغییر','success')},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()}})}};
|
||
const SectionCollapseManager={storageKey:'boardSectionCollapseState',getState(){try{const s=localStorage.getItem(this.storageKey);return s?JSON.parse(s):{products:false,environments:false}}catch{return{products:false,environments:false}}},saveState(s){try{localStorage.setItem(this.storageKey,JSON.stringify(s))}catch(e){console.warn(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){const sid=sec.id.replace('section-','');this.toggle(sid)}}})}};
|
||
const BoardRenderer={render(){const board=document.getElementById('boardArea');const products=projectData.appData.products||[];const fp=products.filter(p=>ToolbarManager.shouldShowProduct(p));let 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.lastDeployedVersions||[]).length,0);h+=`<div class="project-card"><div class="project-card-header"><div class="project-card-title-row"><span class="project-card-icon">📦</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-icon" 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')})}};
|
||
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" style="height:auto;padding:40px;"><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||'#ffffff'};">${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}};
|
||
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',4000);WebhookManager.sendEvent('product_saved',{fileName:a.download})},loadProduct(){const inp=document.createElement('input');inp.type='file';inp.accept='.json,application/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',5000);return}HistoryManager.pushState();projectData.appData=ld;projectData.metadata.updatedAt=new Date().toISOString();updateProjectUI();renderToolGroups();WebhookManager.updateStatusUI();refreshUI();showToast('📂 محصول بارگذاری شد','success',4000);WebhookManager.sendEvent('product_loaded',{fileName:f.name})}catch(err){showToast('❌ خطا: '+err.message,'error',5000)}};r.readAsText(f)});inp.click()},clearAll(){if(!confirm('⚠️ آیا از پاکسازی کامل اطمینان دارید؟'))return;if(!confirm('⚠️ تأیید نهایی: تمام دادهها حذف خواهند شد.'))return;HistoryManager.pushState();projectData.appData={environments:[],products:[]};refreshUI();showToast('🗑️ پاکسازی انجام شد','success',4000);WebhookManager.sendEvent('all_cleared',{})},viewReports(){const p=projectData.appData.products.length;const s=projectData.appData.products.reduce((x,p)=>x+p.projects.length,0);const e=projectData.appData.environments.length;showToast(`📊 محصولات: ${p} | پروژهها: ${s} | محیطها: ${e}`,'info',5000)},showDependencyGraph(){showToast('🕸️ نمودار وابستگی...','info',3000)},defaultAction(tool){showToast(`ابزار: ${tool.title}`,'info',3000)}};
|
||
const WebhookManager={getConfig(){return projectData.webhook},setConfig(c){projectData.webhook={...projectData.webhook,...c};this.updateStatusUI()},isValid(){const c=this.getConfig();if(!c.enabled||!c.url||!c.clientId)return false;return/^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 if(!/^https?:\/\//i.test(c.url)){d.classList.add('error');t.textContent='وضعیت: آدرس نامعتبر'}else{d.classList.add('success');t.textContent='وضعیت: آماده ارسال'}},async sendEvent(en,payload={}){if(!this.isValid())return{success:false};const c=this.getConfig();try{const r=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})});return{success:r.ok,status:r.status}}catch(e){console.error('[Webhook]',e);return{success:false}}}};
|
||
const EnvironmentManager={currentEditEnvId:null,openAddModal(){document.getElementById('newEnvName').value='';document.getElementById('newEnvDesc').value='';document.getElementById('newEnvBgColor').value='#38a169';document.getElementById('newEnvTextColor').value='#ffffff';document.getElementById('newEnvBgColorText').textContent='#38a169';document.getElementById('newEnvTextColorText').textContent='#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');refreshUI();showToast(`محیط "${n}" اضافه شد`,'success');WebhookManager.sendEvent('environment_added',{envName:n})},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';document.getElementById('editEnvBgColorText').textContent=env.backgroundColor||'#38a169';document.getElementById('editEnvTextColorText').textContent=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;p.textContent='نمونه متن'},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');refreshUI();showToast(`محیط "${env.name}" ویرایش شد`,'success');WebhookManager.sendEvent('environment_updated',{envId:env.id,name:env.name})},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);refreshUI();showToast(`محیط "${env.name}" حذف شد`,'warning');WebhookManager.sendEvent('environment_removed',{envId:id})}};
|
||
const ProductManager={currentDetailProductId:null,currentEditProjectId:null,currentDependencyProjectId:null,currentDependencyProjId:null,currentDeploymentProjId:null,currentDeploymentMode:null,currentDeploymentEnvId:null,getTypeInfo(t){return VALID_PROJECT_TYPES.find(x=>x.value===t)||{value:t,label:t,icon:'❓'}},getEnvironmentName(eid){const e=projectData.appData.environments.find(x=>x.id===eid);return e?e.name:eid},getEnvironmentColors(eid){const e=projectData.appData.environments.find(x=>x.id===eid);return{backgroundColor:e?.backgroundColor||'#38a169',textColor:e?.textColor||'#ffffff'}},getEmptyEnvironmentIds(proj){const all=projectData.appData.environments.map(e=>e.id);const dep=(proj.lastDeployedVersions||[]).map(d=>d.environmentId);return all.filter(id=>!dep.includes(id))},normalizeDependency(dep){if(typeof dep==='string')return{projectId:dep,environmentId:null,version:null};return dep},findProjectById(pid){for(const prod of projectData.appData.products){const pr=prod.projects.find(p=>p.id===pid);if(pr)return pr}return null},showDetail(pid){const prod=projectData.appData.products.find(p=>p.id===pid);if(!prod)return;this.currentDetailProductId=pid;document.getElementById('projectDetailTitle').innerHTML=`<span>📦</span><span>${prod.title}</span>`;const projects=prod.projects||[];const tdp=projects.reduce((s,pr)=>s+(pr.lastDeployedVersions||[]).length,0);let h=`<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">${tdp}</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)h+='<div class="empty-subprojects">🌿 هیچ پروژهای نیست</div>';else{h+='<div class="subproject-list">';projects.forEach(pr=>{const ti=this.getTypeInfo(pr.type);const deps=pr.dependencies||[];const dp=pr.lastDeployedVersions||[];const eei=this.getEmptyEnvironmentIds(pr);h+=`<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.openProjectEditModal('${prod.id}','${pr.id}')">✏️</button><button class="btn btn-ghost btn-sm" onclick="ProductManager.openDependencyManager('${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(d=>{const n=this.normalizeDependency(d);const dp2=this.findProjectById(n.projectId);return dp2?dp2.title:n.projectId}).join('، ')}</span></div>`:''}</div>`;h+=`<div class="deployment-section"><div class="deployment-header"><span class="deployment-header-title">🚀 استقرارها (${dp.length})</span>${eei.length>0?`<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(dp.length===0)h+='<div class="empty-deployments">هنوز استقرار ندارد</div>';else{h+='<div class="deployment-list">';dp.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'):'—';h+=`<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.environmentId}','${d.version}','${d.deploymentDate}')">✏️</button><button class="btn btn-danger btn-xs" onclick="ProductManager.removeDeployment('${prod.id}','${pr.id}','${d.environmentId}','${d.version}')">🗑️</button></div></div>`});h+='</div>'}h+='</div></div>'});h+='</div>'}h+='</div></div>';document.getElementById('projectDetailBody').innerHTML=h;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');this.clearProductFormErrors();ModalManager.open('addProductModal');setTimeout(()=>{document.getElementById('newProductTitle').focus()},100)},clearProductFormErrors(){const ti=document.getElementById('newProductTitle'),te=document.getElementById('newProductTitleError');if(ti)ti.classList.remove('error');if(te)te.classList.remove('visible')},createNewProduct(){const t=document.getElementById('newProductTitle').value.trim();if(!t||t.length<3){showToast('عنوان محصول الزامی است (حداقل ۳ کاراکتر)','error');document.getElementById('newProductTitle').classList.add('error');document.getElementById('newProductTitleError').classList.add('visible');return}if(projectData.appData.products.some(p=>p.title.toLowerCase()===t.toLowerCase())){showToast('محصولی با این عنوان وجود دارد','warning');return}HistoryManager.pushState();projectData.appData.products.push({id:`prod-${Date.now()}`,title:t,description:document.getElementById('newProductDescription').value.trim()||'بدون توضیحات',metadata:{createdAt:new Date().toISOString(),owner:document.getElementById('newProductOwner').value.trim()||'',customFields:{}},projects:[]});ModalManager.close('addProductModal');refreshUI();showToast(`محصول "${t}" ایجاد شد`,'success');WebhookManager.sendEvent('product_created',{title:t})},openProductEditModal(){const prod=projectData.appData.products.find(p=>p.id===this.currentDetailProductId);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 prod=projectData.appData.products.find(p=>p.id===this.currentDetailProductId);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();showToast(`محصول "${prod.title}" ویرایش شد`,'success');WebhookManager.sendEvent('product_edited',{productId:this.currentDetailProductId,title:prod.title})},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 prod=projectData.appData.products.find(p=>p.id===this.currentDetailProductId);if(!prod)return;const t=document.getElementById('newProjectTitle').value.trim();const ty=document.getElementById('newProjectType').value;if(!t){showToast('عنوان پروژه الزامی است','error');return}if(!ty){showToast('نوع پروژه الزامی است','error');return}HistoryManager.pushState();const np={id:`proj-${Date.now()}`,title:t,description:document.getElementById('newProjectDescription').value.trim()||'بدون توضیحات',type:ty,repositoryUrl:document.getElementById('newProjectRepo').value.trim(),metadata:{createdAt:new Date().toISOString(),customFields:{}},dependencies:[],lastDeployedVersions:[],deploymentHistory:[]};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();showToast(`پروژه "${t}" ایجاد شد`,'success');WebhookManager.sendEvent('project_added',{productId:this.currentDetailProductId,type:ty,title:t})},openProjectEditModal(pid,projId){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;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 prod=projectData.appData.products.find(p=>p.id===this.currentDetailProductId);if(!prod)return;const pr=prod.projects.find(p=>p.id===this.currentEditProjectId);if(!pr)return;const nt=document.getElementById('editProjTitle').value.trim();const nty=document.getElementById('editProjType').value;if(!nt){showToast('عنوان الزامی است','error');return}if(!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();showToast(`پروژه "${pr.title}" ویرایش شد`,'success');WebhookManager.sendEvent('project_edited',{projId:this.currentEditProjectId,title:pr.title,type:pr.type})},openDependencyManager(pid,projId){this.currentDependencyProjectId=pid;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((dep,i)=>{const n=this.normalizeDependency(dep);const dp=this.findProjectById(n.projectId);const pn=dp?dp.title:n.projectId;const en=n.environmentId?this.getEnvironmentName(n.environmentId):'—';const v=n.version||'—';return`<div class="dependency-item"><div class="dependency-info"><div><div class="dependency-project-name">${pn}</div><div class="dependency-version-info"><span class="deployment-env-tag" style="background-color:${this.getEnvironmentColors(n.environmentId).backgroundColor};color:${this.getEnvironmentColors(n.environmentId).textColor};font-size:9px;padding:1px 6px;">${en}</span> <span style="font-family:monospace;font-weight:600;">v${v}</span></div></div></div><button class="btn btn-danger btn-xs" onclick="ProductManager.removeDependency(${i})">🗑️</button></div>`}).join('')},removeDependency(i){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(i,1);ModalManager.close('dependencyManagerModal');refreshUI();showToast('وابستگی حذف شد','success');WebhookManager.sendEvent('dependency_removed',{projectId:this.currentDependencyProjId})},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 ap=[];projectData.appData.products.forEach(prod=>{prod.projects.forEach(pr=>{if(pr.id!==cpr.id)ap.push({...pr,productName:prod.title})})});if(ap.length===0){rc.innerHTML='<div class="empty-dependencies">پروژه دیگری نیست</div>';return}rc.innerHTML=ap.map(pr=>{const vs=this.getAvailableVersions(pr);return`<div class="dependency-project-result"><div class="dependency-project-header"><span>📦 ${pr.title} <small style="color:var(--text-muted);font-weight:normal;">(${pr.productName})</small></span><span style="font-size:11px;color:var(--text-muted);">${vs.length} نسخه</span></div><div class="dependency-versions-list">${vs.length>0?vs.map(v=>`<div class="dependency-version-item" onclick="ProductManager.addDependency('${pr.id}','${v.environmentId}','${v.version}')"><div><span class="dependency-version-badge ${v.source==='history'?'history':''}">${v.source==='history'?'📜':'🚀'}</span> <span style="font-family:monospace;font-weight:600;">v${v.version}</span> <span style="font-size:11px;color:var(--text-muted);">${this.getEnvironmentName(v.environmentId)}</span></div><span style="font-size:10px;color:var(--text-muted);">${new Date(v.deploymentDate).toLocaleDateString('fa-IR')}</span></div>`).join(''):'<div style="padding:8px;text-align:center;color:var(--text-muted);font-size:11px;">نسخهای نیست</div>'}</div></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}if(q.length<2){document.getElementById('dependencySearchResults').innerHTML='<div class="empty-dependencies">حداقل ۲ کاراکتر</div>';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 ap=[];projectData.appData.products.forEach(prod=>{prod.projects.forEach(pr=>{if(pr.id!==cpr.id)ap.push({...pr,productName:prod.title})})});const fl=ap.filter(pr=>pr.title.toLowerCase().includes(q)||pr.productName.toLowerCase().includes(q)||(pr.description||'').toLowerCase().includes(q));if(fl.length===0){document.getElementById('dependencySearchResults').innerHTML='<div class="empty-dependencies">نتیجهای نیست</div>';return}document.getElementById('dependencySearchResults').innerHTML=fl.map(pr=>{const vs=this.getAvailableVersions(pr);return`<div class="dependency-project-result"><div class="dependency-project-header"><span>📦 ${pr.title} <small style="color:var(--text-muted);font-weight:normal;">(${pr.productName})</small></span><span style="font-size:11px;color:var(--text-muted);">${vs.length} نسخه</span></div><div class="dependency-versions-list">${vs.length>0?vs.map(v=>`<div class="dependency-version-item" onclick="ProductManager.addDependency('${pr.id}','${v.environmentId}','${v.version}')"><div><span class="dependency-version-badge ${v.source==='history'?'history':''}">${v.source==='history'?'📜':'🚀'}</span> <span style="font-family:monospace;font-weight:600;">v${v.version}</span> <span style="font-size:11px;color:var(--text-muted);">${this.getEnvironmentName(v.environmentId)}</span></div><span style="font-size:10px;color:var(--text-muted);">${new Date(v.deploymentDate).toLocaleDateString('fa-IR')}</span></div>`).join(''):'<div style="padding:8px;text-align:center;color:var(--text-muted);font-size:11px;">نسخهای نیست</div>'}</div></div>`}).join('')}},getAvailableVersions(pr){const vs=[];(pr.lastDeployedVersions||[]).forEach(v=>{vs.push({environmentId:v.environmentId,version:v.version,deploymentDate:v.deploymentDate,source:'current'})});(pr.deploymentHistory||[]).forEach(v=>{vs.push({environmentId:v.environmentId,version:v.version,deploymentDate:v.deploymentDate||v.replacedAt,source:'history'})});return vs},addDependency(pid,eid,ver){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 ex=(pr.dependencies||[]).some(dep=>{const n=this.normalizeDependency(dep);return n.projectId===pid&&n.environmentId===eid&&n.version===ver});if(ex){showToast('تکراری است','warning');return}HistoryManager.pushState();if(!pr.dependencies)pr.dependencies=[];pr.dependencies.push({projectId:pid,environmentId:eid,version:ver});ModalManager.close('dependencyManagerModal');refreshUI();showToast('وابستگی اضافه شد','success');WebhookManager.sendEvent('dependency_added',{projectId:this.currentDependencyProjId,dependencyProjectId:pid})},openAddDeploymentModal(pid,projId){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 eei=this.getEmptyEnvironmentIds(pr);if(eei.length===0){showToast('همه محیطها استقرار دارند','warning');return}this.currentDeploymentProjId=projId;this.currentDeploymentMode='add';this.currentDeploymentEnvId=null;document.getElementById('deploymentModalTitle').innerHTML='<span>🚀</span><span>افزودن نسخه استقرار</span>';const envs=projectData.appData.environments.filter(e=>eei.includes(e.id));document.getElementById('deploymentEnvGroup').innerHTML=`<label class="form-label">محیط استقرار <span class="required">*</span></label><select class="form-select" id="depEnvironment"><option value="">انتخاب...</option>${envs.map(e=>`<option value="${e.id}" style="background-color:${e.backgroundColor||'#38a169'};color:${e.textColor||'#ffffff'};">${e.name} - ${e.description}</option>`).join('')}</select>`;document.getElementById('depVersion').value='';document.getElementById('depDate').value='';ModalManager.open('deploymentModal')},openEditDeploymentModal(pid,projId,eid,ver,date){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;this.currentDeploymentProjId=projId;this.currentDeploymentMode='edit';this.currentDeploymentEnvId=eid;document.getElementById('deploymentModalTitle').innerHTML='<span>✏️</span><span>ویرایش نسخه استقرار</span>';const cl=this.getEnvironmentColors(eid);document.getElementById('deploymentEnvGroup').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(eid)}</div>`;document.getElementById('depVersion').value=ver;let dv='';if(date){const d=new Date(date);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;ModalManager.open('deploymentModal')},saveDeployment(){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 ver=document.getElementById('depVersion').value.trim();const date=document.getElementById('depDate').value;if(!ver){showToast('نسخه الزامی است','error');return}if(!date){showToast('تاریخ الزامی است','error');return}const dd=new Date(date).toISOString();HistoryManager.pushState();if(!pr.deploymentHistory)pr.deploymentHistory=[];if(this.currentDeploymentMode==='add'){const eid=document.getElementById('depEnvironment').value;if(!eid){showToast('محیط را انتخاب کنید','error');return}const ei=pr.lastDeployedVersions.findIndex(d=>d.environmentId===eid);if(ei>=0){const old=pr.lastDeployedVersions[ei];pr.deploymentHistory.push({...old,replacedAt:dd,replacedByVersion:ver});pr.lastDeployedVersions[ei]={environmentId:eid,version:ver,deploymentDate:dd};showToast(`v${old.version} → v${ver} در ${this.getEnvironmentName(eid)}`,'success')}else{pr.lastDeployedVersions.push({environmentId:eid,version:ver,deploymentDate:dd});showToast(`v${ver} در ${this.getEnvironmentName(eid)} استقرار یافت`,'success')}}else{const eid=this.currentDeploymentEnvId;const ci=pr.lastDeployedVersions.findIndex(d=>d.environmentId===eid);if(ci>=0){const old=pr.lastDeployedVersions[ci];if(old.version!==ver)pr.deploymentHistory.push({...old,replacedAt:dd,replacedByVersion:ver});pr.lastDeployedVersions[ci]={environmentId:eid,version:ver,deploymentDate:dd};showToast(`v${ver} در ${this.getEnvironmentName(eid)} بهروز شد`,'success')}}ModalManager.close('deploymentModal');refreshUI()},removeDeployment(pid,projId,eid,ver){if(!confirm(`حذف v${ver} از ${this.getEnvironmentName(eid)}؟`))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;HistoryManager.pushState();if(!pr.deploymentHistory)pr.deploymentHistory=[];const di=pr.lastDeployedVersions.findIndex(d=>d.environmentId===eid&&d.version===ver);if(di>=0){pr.deploymentHistory.push({...pr.lastDeployedVersions[di],removedAt:new Date().toISOString(),status:'removed'});pr.lastDeployedVersions.splice(di,1)}refreshUI();showToast(`v${ver} حذف شد`,'warning')},removeProduct(pid){const prod=projectData.appData.products.find(p=>p.id===pid);if(!prod)return;if(!confirm(`حذف "${prod.title}" و تمام پروژهها؟`))return;HistoryManager.pushState();projectData.appData.products=projectData.appData.products.filter(p=>p.id!==pid);ModalManager.close('projectDetailModal');refreshUI();showToast(`"${prod.title}" حذف شد`,'warning');WebhookManager.sendEvent('product_removed',{productId:pid})},removeProject(pid,projId){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;if(!confirm(`حذف "${pr.title}"؟`))return;HistoryManager.pushState();prod.projects=prod.projects.filter(p=>p.id!==projId);refreshUI();showToast(`"${pr.title}" حذف شد`,'warning');WebhookManager.sendEvent('project_removed',{projectId:pid,projId})}};
|
||
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('newProductTitle').addEventListener('input',e=>{if(e.target.value.trim().length>=3){e.target.classList.remove('error');document.getElementById('newProductTitleError').classList.remove('visible')}});
|
||
function handleToolSelect(tool,group){const ah=ActionRegistry[tool.action]||ActionRegistry.defaultAction;ah(tool,group);WebhookManager.sendEvent('tool_selected',{toolId:tool.id,toolTitle:tool.title,toolAction:tool.action,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';if(group.description)he.setAttribute('data-tooltip',group.description);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 class="btn-icon-emoji">${tool.icon||'🔧'}</span><span class="btn-title">${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');WebhookManager.sendEvent('settings_updated',{updatedFields:['webhook']})});
|
||
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>`}
|
||
document.addEventListener('keydown',e=>{if(!(e.ctrlKey||e.metaKey))return;const key=e.key.toLowerCase();for(const group of projectData.toolGroups){const tool=group.tools.find(t=>t.shortcut&&t.shortcut.toLowerCase().replace('ctrl+','')===key);if(tool){e.preventDefault();handleToolSelect(tool,group);break}}});
|
||
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> |