This commit is contained in:
2026-09-18 23:02:30 +03:30
parent 2ad0fbcfb8
commit eb1e805419
6 changed files with 676 additions and 641 deletions
+32 -318
View File
@@ -1730,7 +1730,6 @@
background: rgba(255, 255, 255, 0.2);
margin: 0 4px;
}
/* ✅ NEW: استایل جستجو و فیلتر در نوار ابزار بردها */
.rb-search-box {
position: relative;
display: flex;
@@ -1787,7 +1786,6 @@
align-items: center;
gap: 4px;
}
/* ✅ NEW: نوار فیلتر تگ‌ها */
.rb-tag-filter-bar {
display: flex;
align-items: center;
@@ -2428,9 +2426,6 @@
color: var(--text-muted);
font-size: 12px;
}
/* ═══════════════════════════════════════════════════
✅ NEW: استایل‌های Drag & Drop و فیلتر
═══════════════════════════════════════════════════ */
.card.hidden-by-filter {
display: none !important;
}
@@ -2815,7 +2810,7 @@
</div>
<div class="modal-body" id="projectDetailBody"></div>
<div class="modal-footer">
<button class="btn btn-danger btn-sm" id="projectDetailDeleteBtn">
<button class="btn btn-danger" id="projectDetailDeleteBtn">
🗑️ حذف محصول</button
><button class="btn btn-ghost" data-close-modal>بستن</button>
</div>
@@ -3205,9 +3200,6 @@
<div class="modal-header">
<div class="modal-title">📊 داشبورد مدیریتی</div>
<div class="galaxy-toolbar">
<button class="btn btn-sm" id="dashboardRefresh">
🔄 به‌روزرسانی
</button>
</div>
<button class="modal-close" data-close-modal>✕</button>
</div>
@@ -3227,7 +3219,6 @@
<div class="modal-header">
<div class="modal-title">🔗 تحلیل وابستگی‌ها</div>
<div class="galaxy-toolbar">
<button class="btn btn-sm" id="depAnalysisRefresh">🔄</button>
</div>
<button class="modal-close" data-close-modal>✕</button>
</div>
@@ -3250,7 +3241,6 @@
<div class="modal-header">
<div class="modal-title">🚀 تحلیل استقرار</div>
<div class="galaxy-toolbar">
<button class="btn btn-sm" id="deployAnalysisRefresh">🔄</button>
</div>
<button class="modal-close" data-close-modal>✕</button>
</div>
@@ -3276,7 +3266,6 @@
<div class="modal-header">
<div class="modal-title">🔢 تحلیل نسخه</div>
<div class="galaxy-toolbar">
<button class="btn btn-sm" id="verAnalysisRefresh">🔄</button>
</div>
<button class="modal-close" data-close-modal>✕</button>
</div>
@@ -3296,7 +3285,6 @@
<div class="modal-header">
<div class="modal-title">⚠️ تحلیل ریسک</div>
<div class="galaxy-toolbar">
<button class="btn btn-sm" id="riskAnalysisRefresh">🔄</button>
</div>
<button class="modal-close" data-close-modal>✕</button>
</div>
@@ -3329,7 +3317,6 @@
🏷️ تگ‌ها
</button>
<div class="rb-toolbar-separator"></div>
<!-- ✅ NEW: جستجوی کارت‌ها -->
<div class="rb-search-box">
<span style="opacity: 0.7; font-size: 12px">🔍</span>
<input
@@ -3353,13 +3340,12 @@
>
<div class="rb-toolbar-separator"></div>
<button class="btn" id="rbUndoBtn" title="بازگشت (Ctrl+Z)" disabled>
↩️ بازگشت
↩️
</button>
<button class="btn" id="rbRedoBtn" title="از نو (Ctrl+Y)" disabled>
↪️ از نو
↪️
</button>
</div>
<!-- ✅ NEW: نوار فیلتر تگ‌ها -->
<div class="rb-tag-filter-bar" id="rbTagFilterBar">
<span class="rb-tag-filter-label">🏷️ فیلتر تگ:</span>
<div
@@ -3413,8 +3399,8 @@
</div>
<!-- Footer -->
<div class="modal-footer">
<div class="dashboard-footer-stats" id="rbFooterStats"></div>
<button class="btn btn-ghost" data-close-modal>بستن</button>
<div class="dashboard-footer-stats" id="rbFooterStats"></div>
</div>
</div>
</div>
@@ -3480,31 +3466,31 @@
>
<div
class="rb-preview-column"
style="background: #edf2f7; color: #1a202c"
style="background: #6b7208; color: #b4cbf9"
>
📥 بک لاگ
</div>
<div
class="rb-preview-column"
style="background: #ebf8ff; color: #1a202c"
style="background: #393e65; color: #b4cbf9"
>
📋 برای انجام
</div>
<div
class="rb-preview-column"
style="background: #fef5e7; color: #1a202c"
style="background: #453111; color: #b4cbf9"
>
🔨 در حال انجام
</div>
<div
class="rb-preview-column"
style="background: #faf5ff; color: #1a202c"
style="background: #542881; color: #b4cbf9"
>
🔍 برای تست
</div>
<div
class="rb-preview-column"
style="background: #f0fff4; color: #1a202c"
style="background: #087224; color: #d8d96d"
>
✅ انجام شده
</div>
@@ -3721,10 +3707,10 @@
<div class="rb-tag-list" id="rbTagList"></div>
</div>
<div class="modal-footer">
<button class="btn btn-ghost" data-close-modal>بستن</button>
<div style="font-size: 11px; color: var(--text-muted)">
💡 تغییرات تگ‌ها به صورت خودکار در تمام کارت‌ها اعمال می‌شود
</div>
<button class="btn btn-ghost" data-close-modal>بستن</button>
</div>
</div>
</div>
@@ -3790,178 +3776,22 @@
<div class="modal-title">💾 بازیابی خودکار داده‌ها</div>
</div>
<div class="modal-body">
<div
style="
background: linear-gradient(to left, #ebf8ff, #e6f6ff);
border: 1px solid #90cdf4;
border-right: 4px solid var(--info);
padding: 14px 18px;
border-radius: 8px;
margin-bottom: 16px;
font-size: 13px;
line-height: 1.8;
"
>
<div style="text-align: center; padding: 24px 16px">
<div style="font-size: 48px; margin-bottom: 16px">💾</div>
<div
style="
font-weight: 700;
font-size: 14px;
margin-bottom: 8px;
color: var(--primary);
"
>
⚠️ داده ذخیره‌شده خودکار یافت شد
</div>
<div style="color: var(--text-muted)">
به نظر می‌رسد در جلسه قبلی، تغییراتی در برنامه اعمال شده و به صورت
خودکار ذخیره شده‌اند.
</div>
<div
style="
margin-top: 10px;
font-size: 12px;
color: var(--text-muted);
"
>
📅 تاریخ ذخیره:
<strong id="rbAutosaveDate" style="color: var(--primary)"
>—</strong
>
</div>
</div>
<div
style="
background: var(--border-light);
border: 1px solid var(--border);
border-radius: 8px;
padding: 12px;
margin-bottom: 16px;
"
>
<div
style="
font-weight: 700;
font-size: 13px;
margin-bottom: 10px;
color: var(--primary);
color: var(--text);
margin-bottom: 8px;
"
>
📊 محتویات داده‌های ذخیره‌شده:
داده‌هایی در حافظه موقت وجود دارند
</div>
<div
style="
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
font-size: 12px;
"
>
<div
style="
display: flex;
justify-content: space-between;
padding: 6px 10px;
background: var(--card-bg);
border-radius: 6px;
"
>
<span>🌍 محیط‌ها:</span
><strong id="rbAutosaveEnvCount" style="color: var(--primary)"
>0</strong
>
</div>
<div
style="
display: flex;
justify-content: space-between;
padding: 6px 10px;
background: var(--card-bg);
border-radius: 6px;
"
>
<span>📦 محصولات:</span
><strong id="rbAutosaveProdCount" style="color: var(--primary)"
>0</strong
>
</div>
<div
style="
display: flex;
justify-content: space-between;
padding: 6px 10px;
background: var(--card-bg);
border-radius: 6px;
"
>
<span>🌿 پروژه‌ها:</span
><strong id="rbAutosaveProjCount" style="color: var(--primary)"
>0</strong
>
</div>
<div
style="
display: flex;
justify-content: space-between;
padding: 6px 10px;
background: var(--card-bg);
border-radius: 6px;
"
>
<span>🚀 استقرارها:</span
><strong id="rbAutosaveDepCount" style="color: var(--primary)"
>0</strong
>
</div>
<div
style="
display: flex;
justify-content: space-between;
padding: 6px 10px;
background: var(--card-bg);
border-radius: 6px;
"
>
<span>📋 بردها:</span
><strong id="rbAutosaveBoardCount" style="color: var(--primary)"
>0</strong
>
</div>
<div
style="
display: flex;
justify-content: space-between;
padding: 6px 10px;
background: var(--card-bg);
border-radius: 6px;
"
>
<span>🏷️ تگ‌های برد:</span
><strong id="rbAutosaveTagCount" style="color: var(--primary)"
>0</strong
>
</div>
<div style="font-size: 12px; color: var(--text-muted)">
آیا مایل به بازیابی آن‌ها هستید؟
</div>
</div>
<div style="font-size: 13px; color: var(--text); line-height: 1.8">
آیا می‌خواهید این داده‌ها را بازیابی کنید؟
<ul
style="
margin-top: 8px;
padding-right: 20px;
color: var(--text-muted);
font-size: 12px;
"
>
<li>
<strong>بازیابی:</strong> تمام داده‌های ذخیره‌شده جایگزین
داده‌های فعلی می‌شوند
</li>
<li>
<strong>نادیده گرفتن:</strong> داده‌های خودکار حذف شده و
داده‌های فعلی حفظ می‌شوند
</li>
</ul>
</div>
</div>
<div class="modal-footer" style="justify-content: space-between">
<button class="btn btn-accent" id="rbRecoverAutosaveBtn">
@@ -4031,7 +3861,7 @@
projectName: "xReleaseAssistant",
projectTitle: "دستیار انتشار",
description: "مدیریت نسخه‌ها و نیازمندی‌های محصولات",
version: "1.2.0",
version: "1.1.0",
developer: "هادی خزاعی اصل",
company: "رایان پرداز کاوش",
metadata: {
@@ -4091,7 +3921,6 @@
action: "showReleaseBoards",
description: "مشاهده و مدیریت بردهای انتشار",
},
/* ✅ NEW: ابزار مدیریت تگ‌ها */
{
id: "tag-manager",
title: "مدیریت تگ‌ها",
@@ -4153,50 +3982,7 @@
},
],
appData: {
environments: [
{
id: "env-prod-001",
name: "Production",
description: "محیط عملیاتی اصلی - در دسترس کاربران نهایی",
backgroundColor: "#38a169",
textColor: "#ffffff",
},
{
id: "env-stage-002",
name: "Staging",
description: "محیط پیش از تولید - شبیه‌سازی Production",
backgroundColor: "#dd6b20",
textColor: "#ffffff",
},
{
id: "env-dev-003",
name: "Development",
description: "محیط توسعه - برای تیم فنی",
backgroundColor: "#3182ce",
textColor: "#ffffff",
},
{
id: "env-qa-004",
name: "QA",
description: "محیط تست کیفیت - کنترل کیفیت خودکار و دستی",
backgroundColor: "#805ad5",
textColor: "#ffffff",
},
{
id: "env-uat-005",
name: "UAT",
description: "تست پذیرش کاربر - تأیید کارفرما",
backgroundColor: "#d69e2e",
textColor: "#ffffff",
},
{
id: "env-dr-006",
name: "Disaster Recovery",
description: "محیط بازیابی پس از بحران - پشتیبان Production",
backgroundColor: "#e53e3e",
textColor: "#ffffff",
},
],
environments: [],
products: [],
boards: [],
boardTags: [],
@@ -4546,7 +4332,7 @@
getSectionHTML() {
const d = this.getData();
let h = `<div class="board-section" id="section-dashboard">`;
h += `<div class="board-header"><div class="board-header-title"><span class="collapse-icon">▼</span>📊 داشبورد مدیریتی</div><div class="board-header-actions"><button class="btn btn-accent btn-sm" onclick="DashboardManager.open()">📊 مشاهده کامل</button></div></div>`;
h += `<div class="board-header"><div class="board-header-title"><span class="collapse-icon">▼</span>📊 داشبورد مدیریتی</div><div class="board-header-actions"><button class="btn btn-accent" onclick="DashboardManager.open()">📊 مشاهده کامل</button></div></div>`;
h += `<div class="section-content">`;
h += '<div class="dashboard-kpi-row" style="margin-bottom:16px">';
h += `<div class="dashboard-kpi-card"><div class="dashboard-kpi-icon">📦</div><div class="dashboard-kpi-value">${d.products}</div><div class="dashboard-kpi-label">محصول</div></div>`;
@@ -4972,7 +4758,6 @@
showReleaseBoards() {
ReleaseBoardsManager.open();
},
/* ✅ NEW: اکشن مدیریت تگ‌ها */
showTagManager() {
ReleaseBoardsManager.openTagManager();
},
@@ -5969,8 +5754,6 @@
: 0;
document.getElementById("dashboardFooterStats").innerHTML =
`<span>📦 محصولات: <span class="stat-highlight">${d.products}</span></span><span>🌿 پروژه‌ها: <span class="stat-highlight">${d.projects}</span></span><span>🚀 استقرارها: <span class="stat-highlight">${d.deploys}</span></span><span>📈 پوشش استقرار: <span class="stat-highlight">${coverage}%</span></span>`;
document.getElementById("dashboardRefresh").onclick = () =>
this.render();
},
renderDonut(cid, data) {
const c = document.getElementById(cid);
@@ -6724,53 +6507,12 @@
checkRecovery() {
const saved = this.load();
if (!saved || !saved.appData) return;
const appData = saved.appData;
const stats = {
environments: (appData.environments || []).length,
products: (appData.products || []).length,
projects: 0,
deployments: 0,
boards: (appData.boards || []).length,
boardTags: (appData.boardTags || []).length,
};
(appData.products || []).forEach((prod) => {
stats.projects += (prod.projects || []).length;
(prod.projects || []).forEach((pr) => {
stats.deployments += (pr.deployments || []).filter(
(d) => d.status === "active",
).length;
});
});
const dateEl = document.getElementById("rbAutosaveDate");
const envCountEl = document.getElementById("rbAutosaveEnvCount");
const prodCountEl = document.getElementById("rbAutosaveProdCount");
const projCountEl = document.getElementById("rbAutosaveProjCount");
const depCountEl = document.getElementById("rbAutosaveDepCount");
const boardCountEl = document.getElementById("rbAutosaveBoardCount");
if (dateEl) {
const d = new Date(saved.savedAt);
dateEl.textContent =
d.toLocaleDateString("fa-IR") +
" - " +
d.toLocaleTimeString("fa-IR", {
hour: "2-digit",
minute: "2-digit",
});
}
if (envCountEl)
envCountEl.textContent = stats.environments.toLocaleString("fa-IR");
if (prodCountEl)
prodCountEl.textContent = stats.products.toLocaleString("fa-IR");
if (projCountEl)
projCountEl.textContent = stats.projects.toLocaleString("fa-IR");
if (depCountEl)
depCountEl.textContent = stats.deployments.toLocaleString("fa-IR");
if (boardCountEl)
boardCountEl.textContent = stats.boards.toLocaleString("fa-IR");
document.getElementById("rbRecoverAutosaveBtn").onclick = () =>
this.recover();
document.getElementById("rbDiscardAutosaveBtn").onclick = () =>
this.discard();
setTimeout(() => {
ModalManager.open("rbAutosaveModal");
}, 600);
@@ -6825,28 +6567,28 @@
DEFAULT_COLUMNS: [
{
title: "📥 بک لاگ",
backgroundColor: "#edf2f7",
textColor: "#1a202c",
backgroundColor: "#6b7208",
textColor: "#b4cbf9",
},
{
title: "📋 برای انجام",
backgroundColor: "#ebf8ff",
textColor: "#1a202c",
backgroundColor: "#393e65",
textColor: "#b4cbf9",
},
{
title: "🔨 در حال انجام",
backgroundColor: "#fef5e7",
textColor: "#1a202c",
backgroundColor: "#453111",
textColor: "#b4cbf9",
},
{
title: "🔍 برای تست",
backgroundColor: "#faf5ff",
textColor: "#1a202c",
backgroundColor: "#542881",
textColor: "#b4cbf9",
},
{
title: "✅ انجام شده",
backgroundColor: "#f0fff4",
textColor: "#1a202c",
backgroundColor: "#087224",
textColor: "#d8d96d",
},
],
open() {
@@ -6897,7 +6639,6 @@
} else {
this._incrementalRender(board);
}
/* ✅ NEW: اعمال فیلترها و اتصال درگ پس از رندر */
this.applyFilters();
this.initDragAndDrop();
this.updateFooterStats();
@@ -7098,9 +6839,6 @@ ${checklistTotal > 0 ? `<span class="card-meta-item">✅ ${checklistDone}/${chec
${checklistHtml}
</div>`;
},
/* ═══════════════════════════════════════════════════
✅ NEW: فیلتر تگ‌ها
═══════════════════════════════════════════════════ */
renderFilterBar() {
const container = document.getElementById("rbTagFilterContainer");
const clearBtn = document.getElementById("rbClearTagFilter");
@@ -7138,9 +6876,6 @@ ${checklistHtml}
};
}
},
/* ═══════════════════════════════════════════════════
✅ NEW: اعمال فیلتر و جستجو
═══════════════════════════════════════════════════ */
applyFilters() {
const board = this.getActiveBoard();
if (!board) return;
@@ -7224,9 +6959,6 @@ ${checklistHtml}
}
}
},
/* ═══════════════════════════════════════════════════
✅ NEW: رویدادهای جستجو
═══════════════════════════════════════════════════ */
bindSearchEvents() {
const inp = document.getElementById("rbCardSearchInput");
const clr = document.getElementById("rbCardSearchClear");
@@ -7253,9 +6985,6 @@ ${checklistHtml}
inp.focus();
};
},
/* ═══════════════════════════════════════════════════
✅ NEW: Drag & Drop
═══════════════════════════════════════════════════ */
initDragAndDrop() {
const wrapper = document.getElementById("rbBoardWrapper");
if (!wrapper) return;
@@ -7350,7 +7079,6 @@ ${checklistHtml}
.querySelectorAll(".drag-over-column")
.forEach((el) => el.classList.remove("drag-over-column"));
},
/* ── جابجایی کارت بین ستون‌ها ── */
moveCard(cardId, targetColumnId) {
const board = this.getActiveBoard();
if (!board) return;
@@ -7387,7 +7115,6 @@ ${checklistHtml}
2000,
);
},
/* ── جابجایی ستون‌ها ── */
moveColumn(sourceColId, targetColId) {
const board = this.getActiveBoard();
if (!board) return;
@@ -7406,7 +7133,6 @@ ${checklistHtml}
this.render(true);
showToast("📊 ستون جابجا شد", "success", 2000);
},
/* ── مدیریت برد ── */
getActiveBoard() {
return (projectData.appData.boards || []).find(
(b) => b.id === this.activeBoardId,
@@ -7525,7 +7251,6 @@ ${checklistHtml}
showToast("برد حذف شد", "warning");
this.render(true);
},
/* ── مدیریت ستون ── */
openNewColumnModal() {
if (!this.getActiveBoard()) {
showToast("ابتدا یک برد ایجاد کنید", "warning");
@@ -7600,7 +7325,6 @@ ${checklistHtml}
showToast("ستون حذف شد", "warning");
this.render(true);
},
/* ── مدیریت کارت ── */
openNewCardModal(columnId) {
this.editing.cardId = null;
this.editing.columnForCard = columnId;
@@ -7818,7 +7542,6 @@ ${checklistHtml}
document.getElementById("rbDeleteCardBtn").onclick = () =>
this.deleteCard();
},
/* ── Toolbar Events ── */
bindToolbarEvents() {
document.getElementById("rbEditBoard").onclick = () =>
this.openEditBoardModal();
@@ -7852,7 +7575,6 @@ ${checklistHtml}
document.getElementById("rbFooterStats").innerHTML =
`<span>📋 بردها: <span class="stat-highlight">${boards.length}</span></span><span>📑 ستون‌ها: <span class="stat-highlight">${boards.reduce((s, b) => s + b.columns.length, 0)}</span></span><span>📝 کارت‌ها: <span class="stat-highlight">${totalCards}</span></span>`;
},
/* ── Tag Manager ── */
openTagManager() {
ModalManager.open("rbTagManagerModal");
this.renderTagList();
@@ -8089,8 +7811,6 @@ ${checklistHtml}
this.renderRiskList("depRiskList", d);
document.getElementById("depAnalysisFooterStats").innerHTML =
`<span>🔗 کل: <span class="stat-highlight">${d.totalDeps}</span></span><span>⚠️ گلوگاه: <span class="stat-highlight">${d.bottlenecks.length}</span></span><span>🔴 شکسته: <span class="stat-highlight">${d.brokenDeps}</span></span><span>🔵 ایزوله: <span class="stat-highlight">${d.isolated.length}</span></span>`;
document.getElementById("depAnalysisRefresh").onclick = () =>
this.render();
},
renderHubChart(cid, bottlenecks) {
const c = document.getElementById(cid);
@@ -8463,8 +8183,6 @@ ${checklistHtml}
this.renderRiskList("deployRiskList", d.riskList);
document.getElementById("deployAnalysisFooterStats").innerHTML =
`<span>🚀 کل: <span class="stat-highlight">${d.total}</span></span><span>📈 پوشش: <span class="stat-highlight">${d.coverage}%</span></span><span>⚠️ ریسک‌ها: <span class="stat-highlight">${d.riskList.length}</span></span>`;
document.getElementById("deployAnalysisRefresh").onclick = () =>
this.render();
},
renderEnvBarChart(cid, edc) {
const c = document.getElementById(cid);
@@ -8888,8 +8606,6 @@ ${checklistHtml}
this.renderRiskList("verRiskList", d.riskList);
document.getElementById("verAnalysisFooterStats").innerHTML =
`<span>🔢 کل: <span class="stat-highlight">${d.total}</span></span><span>📦 یکتا: <span class="stat-highlight">${d.uniqueVersions}</span></span><span>📈 میانگین: <span class="stat-highlight">${d.avgVersion}</span></span><span>⚠️ ریسک‌ها: <span class="stat-highlight">${d.riskList.length}</span></span>`;
document.getElementById("verAnalysisRefresh").onclick = () =>
this.render();
},
renderMajorBarChart(cid, majorCounts) {
const c = document.getElementById(cid);
@@ -9274,8 +8990,6 @@ ${checklistHtml}
this.renderRiskList("riskDetailList", d.risks);
document.getElementById("riskAnalysisFooterStats").innerHTML =
`<span>🎯 سلامت: <span class="stat-highlight">${d.healthScore}%</span></span><span>⚠️ ریسک‌ها: <span class="stat-highlight">${d.total}</span></span><span>🔴 بحرانی: <span class="stat-highlight">${d.crit}</span></span><span>🟠 بالا: <span class="stat-highlight">${d.high}</span></span>`;
document.getElementById("riskAnalysisRefresh").onclick = () =>
this.render();
},
renderSeverityDonut(cid, d) {
const c = document.getElementById(cid);