Files
xSaherelmWorkspace/Documents/Resources/غالب پروژه ها.html
2026-09-19 10:35:32 +03:30

1322 lines
41 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>پروژه نمونه</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
/* ============ Theme Variables ============ */
:root {
--primary: #1a365d;
--primary-light: #2c5282;
--primary-dark: #0f2341;
--accent: #c9a961;
--accent-dark: #a8893f;
--accent-light: #e0c789;
--bg: #f5f7fa;
--board-bg: #e2e8f0;
--card-bg: #ffffff;
--column-bg: #edf2f7;
--panel-bg: #ffffff;
--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,0.08), 0 10px 20px rgba(26,54,93,0.1);
--shadow-sm: 0 2px 4px rgba(0,0,0,0.06);
--shadow-lg: 0 10px 30px rgba(0,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;
--column-bg: #374151;
--panel-bg: #1f2937;
--text: #e2e8f0;
--text-muted: #cbd5e0;
--text-light: #a0aec0;
--border: #4a5568;
--border-light: #374151;
--shadow: 0 4px 6px rgba(0,0,0,0.3), 0 10px 20px rgba(0,0,0,0.4);
--shadow-sm: 0 2px 4px rgba(0,0,0,0.2);
--shadow-lg: 0 10px 30px rgba(0,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 0.3s, color 0.3s;
}
/* ============ Header ============ */
.app-header {
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-light) 100%);
color: white;
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,0.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,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: -0.3px;
color: white;
}
.app-subtitle {
font-size: 11px;
opacity: 0.85;
color: rgba(255,255,255,0.9);
}
/* ============ Buttons ============ */
.btn {
background: linear-gradient(135deg, var(--primary-light), var(--primary));
color: white;
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 0.2s;
box-shadow: 0 2px 4px rgba(0,0,0,0.15);
white-space: nowrap;
}
.btn:hover { transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0,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,0.08);
border: 1px solid var(--border);
color: var(--primary);
font-weight: 600;
}
.btn-ghost:hover {
background: rgba(26,54,93,0.15);
border-color: var(--primary-light);
}
[data-theme="dark"] .btn-ghost {
background: rgba(255,255,255,0.1);
border: 1px solid rgba(255,255,255,0.25);
color: white;
}
[data-theme="dark"] .btn-ghost:hover {
background: rgba(255,255,255,0.2);
}
/* ============ Main Layout ============ */
.app-main {
flex: 1;
display: flex;
overflow: hidden;
position: relative;
flex-direction: row;
}
/* ============ Sidebar ============ */
.sidebar {
order: -1;
width: var(--sidebar-width);
background: var(--panel-bg);
border-right: 1px solid var(--border);
display: flex;
flex-direction: column;
transition: width 0.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: white;
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: white;
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 0.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 0.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 0.2s;
text-align: right;
width: 100%;
position: relative;
}
.sidebar-btn:hover {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
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;
}
.sidebar-btn .shortcut-badge {
font-size: 9px;
font-weight: 700;
padding: 2px 6px;
background: rgba(0,0,0,0.08);
color: var(--text-muted);
border-radius: 4px;
font-family: 'Vazirmatn', monospace;
flex-shrink: 0;
letter-spacing: 0.5px;
}
.sidebar-btn:hover .shortcut-badge {
background: rgba(255,255,255,0.2);
color: white;
}
/* ============ Tooltip ============ */
[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: white;
padding: 8px 12px;
border-radius: 6px;
font-size: 11px;
font-weight: 500;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s;
z-index: 1000;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
max-width: 250px;
white-space: normal;
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 0.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 ============ */
.tabs-bar {
background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary) 100%);
border-bottom: 2px solid var(--accent);
display: flex;
align-items: center;
padding: 0 10px;
min-height: var(--tabs-height);
box-shadow: var(--shadow-sm);
flex-shrink: 0;
gap: 6px;
position: relative;
}
[data-theme="dark"] .tabs-bar {
background: linear-gradient(135deg, #0f1724 0%, #1a2a44 100%);
}
.tab-control-btn {
background: rgba(255,255,255,0.1);
border: 1px solid rgba(255,255,255,0.15);
color: white;
width: 32px; height: 32px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
padding: 0;
}
.tab-control-btn:hover:not(:disabled) {
background: var(--accent);
color: var(--primary-dark);
border-color: var(--accent);
transform: translateY(-1px);
}
/* ============ Board Area ============ */
.board-area {
flex: 1;
overflow: auto;
padding: 20px;
background: var(--board-bg);
transition: background 0.3s;
}
.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: 0.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; }
/* ============ Modals ============ */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(26,54,93,0.6);
backdrop-filter: blur(3px);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 20px;
animation: fadeIn 0.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 0.25s;
}
.modal-lg { max-width: 800px; }
@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: white;
padding: 16px 20px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 3px solid var(--accent);
}
.modal-title {
font-size: 16px;
font-weight: 700;
display: flex;
align-items: center;
gap: 10px;
}
.modal-close {
background: rgba(255,255,255,0.15);
border: none;
color: white;
width: 30px; height: 30px;
border-radius: 50%;
cursor: pointer;
font-size: 16px;
transition: all 0.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);
}
/* ============ Forms ============ */
.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 0.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,0.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;
}
/* ============ Webhook Section ============ */
.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: 0.3s;
border-radius: 20px;
}
.slider::before {
position: absolute;
content: "";
height: 14px;
width: 14px;
right: 3px;
bottom: 3px;
background: white;
transition: 0.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: 0.4; }
}
/* ============ Toast ============ */
.toast-container {
position: fixed;
bottom: 20px;
left: 20px;
z-index: 2000;
display: flex;
flex-direction: column;
gap: 8px;
}
.toast {
background: var(--primary);
color: white;
padding: 12px 18px;
border-radius: 8px;
box-shadow: var(--shadow-lg);
font-size: 13px;
display: flex;
align-items: center;
gap: 10px;
animation: slideInLeft 0.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); }
}
/* ============ Footer ============ */
.app-footer {
background: linear-gradient(135deg, var(--primary), var(--primary-light));
color: white;
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; }
/* ============ Readonly Fields Style ============ */
.form-input[readonly],
.form-textarea[readonly] {
background: var(--border-light);
color: var(--text-muted);
cursor: not-allowed;
opacity: 0.85;
border-style: dashed;
}
.form-input[readonly]:focus,
.form-textarea[readonly]:focus {
border-color: var(--border);
box-shadow: none;
}
.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, 0.12);
border-color: rgba(49, 130, 206, 0.3);
border-right-color: var(--info);
}
/* ============ Scrollbar ============ */
::-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); }
</style>
</head>
<body>
<!-- Header -->
<header class="app-header">
<div class="app-brand">
<div class="app-logo">📊</div>
<div class="app-brand-text">
<div class="app-title">پروژه نمونه</div>
<div class="app-subtitle">توضیحات پروژه</div>
</div>
</div>
</header>
<!-- Toolbar -->
<div class="tabs-bar">
<button class="tab-control-btn" id="sidebarToggle" title="باز/بسته کردن سایدبار">
<span id="sidebarIcon">☰</span>
</button>
<button class="tab-control-btn" id="themeToggle" title="تغییر تم">
<span id="themeIcon">🌙</span>
</button>
</div>
<!-- Main Layout -->
<div class="app-main">
<!-- Sidebar -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-header">
<div class="sidebar-title">🛠️ ابزارها</div>
</div>
<div class="sidebar-body" id="sidebarBody"></div>
</aside>
<!-- Board Area -->
<main class="board-area">
<div class="board-empty">
<div class="board-empty-icon">📋</div>
<h2>ناحیه اصلی</h2>
<p>محتوای اصلی برنامه در این ناحیه نمایش داده می‌شود</p>
</div>
</main>
</div>
<!-- Footer -->
<footer class="app-footer">
<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.0.0
</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>تنظیمات وب‌هوک (Webhook)</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 class="form-hint">آدرسی که رویدادها به آن ارسال می‌شوند (باید با http یا https شروع شود)</div>
</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 class="form-hint">این شناسه در هدر درخواست با کلید X-Client-Id ارسال می‌شود</div>
</div>
<div class="webhook-status" id="webhookStatus">
<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>
<!-- Toast Container -->
<div class="toast-container" id="toastContainer"></div>
<script>
// ============ Project Data Structure (New Schema) ============
const projectData = {
projectId: "550e8400-e29b-41d4-a716-446655440000",
projectName: "sample_project",
projectTitle: "پروژه نمونه",
description: "توضیحات پروژه",
version: "1.0.0",
developer: "هادی خزاعی اصل",
company: "رایان پرداز کاوش",
webhook: {
url: "",
clientId: "",
enabled: false
},
toolGroups: [
{
id: 'project-management',
title: 'پروژه',
icon: '📁',
description: 'مدیریت چرخه حیات پروژه',
tools: [
{
id: 'project-settings',
title: 'تنظیمات پروژه',
icon: '⚙️',
action: 'openSettings',
shortcut: 'Ctrl+,',
description: 'مشاهده اطلاعات پروژه و تنظیمات وب‌هوک',
targetEntity: 'settings'
},
{
id: 'create-project',
title: 'ایجاد پروژه جدید',
icon: '➕',
action: 'createProject',
shortcut: 'Ctrl+N',
description: 'ایجاد یک پروژه جدید با متادیتای اولیه',
targetEntity: 'projects'
}
]
},
{
id: 'data-analytics',
title: 'ابزارهای تحلیل داده',
icon: '📊',
description: 'ابزارهای تحلیل و پردازش داده‌ها',
tools: [
{
id: 'chart-generator',
title: 'نمودار ساز پویا',
icon: '📈',
action: 'generateChart',
shortcut: 'Ctrl+G',
description: 'ابزاری برای تبدیل جداول داده به نمودارهای میله‌ای و دایره‌ای به صورت زنده',
targetEntity: 'charts'
},
{
id: 'advanced-filter',
title: 'فیلتر پیشرفته',
icon: '🔍',
action: 'applyFilter',
shortcut: 'Ctrl+F',
description: 'امکان اعمال فیلترهای چندلایه و شرطی بر روی مجموعه داده‌ها',
targetEntity: 'datasets'
}
]
},
{
id: 'export-tools',
title: 'ابزارهای خروجی و گزارش',
icon: '📤',
description: 'ابزارهای تولید گزارش و خروجی',
tools: [
{
id: 'pdf-export',
title: 'خروجی PDF',
icon: '📄',
action: 'exportPDF',
shortcut: 'Ctrl+E',
description: 'تولید فایل PDF استاندارد از گزارش‌های فعلی با قابلیت شخصی‌سازی هدر و فوتر',
targetEntity: 'reports'
}
]
}
]
};
// ============ Action Registry ============
const ActionRegistry = {
openSettings(tool, group) {
openSettingsModal();
},
createProject(tool, group) {
showToast(`🚀 اکشن "${tool.action}" اجرا شد - ${tool.title}`, 'info', 3000);
},
generateChart(tool, group) {
showToast(`📈 اکشن "${tool.action}" اجرا شد - ${tool.title}`, 'info', 3000);
},
applyFilter(tool, group) {
showToast(`🔍 اکشن "${tool.action}" اجرا شد - ${tool.title}`, 'info', 3000);
},
exportPDF(tool, group) {
showToast(`📄 اکشن "${tool.action}" اجرا شد - ${tool.title}`, 'info', 3000);
},
defaultAction(tool, group) {
showToast(`ابزار انتخاب شد: ${tool.title}`, 'info', 3000);
}
};
// ============ Toast System ============
function showToast(message, type = 'info', duration = 3000) {
const container = document.getElementById('toastContainer');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
const icons = {
success: '✅',
error: '❌',
warning: '⚠️',
info: 'ℹ️'
};
toast.innerHTML = `
<span>${icons[type] || 'ℹ️'}</span>
<span>${message}</span>
`;
container.appendChild(toast);
setTimeout(() => {
toast.style.transition = 'opacity 0.3s, transform 0.3s';
toast.style.opacity = '0';
toast.style.transform = 'translateX(-30px)';
setTimeout(() => toast.remove(), 300);
}, duration);
}
// ============ Webhook Manager ============
const WebhookManager = {
getConfig() {
return projectData.webhook;
},
setConfig(config) {
projectData.webhook = { ...projectData.webhook, ...config };
this.updateStatusUI();
},
isValid() {
const cfg = this.getConfig();
if (!cfg.enabled) return false;
if (!cfg.url || !cfg.clientId) return false;
if (!/^https?:\/\//i.test(cfg.url)) return false;
return true;
},
updateStatusUI() {
const cfg = this.getConfig();
const dot = document.getElementById('webhookStatusDot');
const text = document.getElementById('webhookStatusText');
if (!dot || !text) return;
dot.className = 'webhook-status-dot';
if (!cfg.enabled) {
text.textContent = 'وضعیت: غیرفعال';
} else if (!cfg.url || !cfg.clientId) {
dot.classList.add('pending');
text.textContent = 'وضعیت: تنظیمات ناقص';
} else if (!/^https?:\/\//i.test(cfg.url)) {
dot.classList.add('error');
text.textContent = 'وضعیت: آدرس نامعتبر';
} else {
dot.classList.add('success');
text.textContent = 'وضعیت: آماده ارسال';
}
},
async sendEvent(eventName, payload = {}) {
if (!this.isValid()) {
console.warn('[Webhook] ارسال انجام نشد - تنظیمات معتبر نیست');
return { success: false, reason: 'invalid_config' };
}
const cfg = this.getConfig();
const requestBody = {
event: eventName,
timestamp: new Date().toISOString(),
projectId: projectData.projectId,
projectName: projectData.projectName,
projectVersion: projectData.version,
clientId: cfg.clientId,
data: payload
};
try {
const response = await fetch(cfg.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Client-Id': cfg.clientId,
'X-Event-Name': eventName,
'X-Project-Id': projectData.projectId
},
body: JSON.stringify(requestBody)
});
const success = response.ok;
if (success) {
console.log(`[Webhook] رویداد "${eventName}" با موفقیت ارسال شد`);
} else {
console.error(`[Webhook] خطا در ارسال: ${response.status}`);
}
return { success, status: response.status };
} catch (err) {
console.error('[Webhook] خطای شبکه:', err);
return { success: false, reason: 'network_error', error: err.message };
}
}
};
// ============ Tool Selection Handler ============
function handleToolSelect(tool, group) {
const actionHandler = ActionRegistry[tool.action] || ActionRegistry.defaultAction;
actionHandler(tool, group);
const eventPayload = {
toolId: tool.id,
toolTitle: tool.title,
toolIcon: tool.icon,
toolAction: tool.action,
toolDescription: tool.description,
targetEntity: tool.targetEntity,
groupId: group.id,
groupTitle: group.title,
selectedAt: new Date().toISOString()
};
WebhookManager.sendEvent('tool_selected', eventPayload);
}
// ============ Theme Toggle ============
const themeToggle = document.getElementById('themeToggle');
const themeIcon = document.getElementById('themeIcon');
const html = document.documentElement;
const savedTheme = localStorage.getItem('theme') || 'light';
html.setAttribute('data-theme', savedTheme);
updateThemeUI(savedTheme);
themeToggle.addEventListener('click', () => {
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
updateThemeUI(newTheme);
});
function updateThemeUI(theme) {
themeIcon.textContent = theme === 'dark' ? '☀️' : '🌙';
}
// ============ Sidebar Toggle ============
const sidebarToggle = document.getElementById('sidebarToggle');
const sidebar = document.getElementById('sidebar');
const sidebarIcon = document.getElementById('sidebarIcon');
sidebarToggle.addEventListener('click', () => {
sidebar.classList.toggle('collapsed');
sidebarIcon.textContent = sidebar.classList.contains('collapsed') ? '☰' : '✕';
});
// ============ Render Tool Groups ============
const sidebarBody = document.getElementById('sidebarBody');
function renderToolGroups() {
sidebarBody.innerHTML = '';
projectData.toolGroups.forEach(group => {
const groupElement = document.createElement('div');
groupElement.className = 'sidebar-group';
groupElement.dataset.groupId = group.id;
const headerElement = document.createElement('div');
headerElement.className = 'sidebar-group-header';
// Add tooltip if description exists
if (group.description) {
headerElement.setAttribute('data-tooltip', group.description);
}
headerElement.innerHTML = `
<div class="group-icon">
<span>${group.icon || '📁'}</span>
<span>${group.title}</span>
</div>
<span class="group-arrow">▼</span>
`;
const bodyElement = document.createElement('div');
bodyElement.className = 'sidebar-group-body';
group.tools.forEach(tool => {
const toolBtn = document.createElement('button');
toolBtn.className = 'sidebar-btn';
toolBtn.dataset.toolId = tool.id;
toolBtn.title = tool.description;
toolBtn.innerHTML = `
<span class="btn-icon-emoji">${tool.icon || '🔧'}</span>
<span class="btn-title">${tool.title}</span>
${tool.shortcut ? `<span class="shortcut-badge">${tool.shortcut}</span>` : ''}
`;
toolBtn.addEventListener('click', () => {
handleToolSelect(tool, group);
});
bodyElement.appendChild(toolBtn);
});
headerElement.addEventListener('click', () => {
groupElement.classList.toggle('collapsed');
});
groupElement.appendChild(headerElement);
groupElement.appendChild(bodyElement);
sidebarBody.appendChild(groupElement);
});
}
// ============ Settings Modal ============
const settingsModal = document.getElementById('settingsModal');
const saveSettingsBtn = document.getElementById('saveSettingsBtn');
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();
settingsModal.classList.add('active');
}
function closeSettingsModal() {
settingsModal.classList.remove('active');
}
document.querySelectorAll('[data-close-modal]').forEach(btn => {
btn.addEventListener('click', closeSettingsModal);
});
settingsModal.addEventListener('click', (e) => {
if (e.target === settingsModal) closeSettingsModal();
});
['settingProjectTitle', 'settingVersion', 'settingDescription', 'settingDeveloper', 'settingCompany'].forEach(id => {
const el = document.getElementById(id);
el.addEventListener('keydown', (e) => {
if (!e.ctrlKey && !e.metaKey && e.key.length === 1) {
e.preventDefault();
}
});
el.addEventListener('paste', (e) => e.preventDefault());
});
['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
});
});
});
saveSettingsBtn.addEventListener('click', () => {
WebhookManager.setConfig({
url: document.getElementById('settingWebhookUrl').value.trim(),
clientId: document.getElementById('settingWebhookClientId').value.trim(),
enabled: document.getElementById('settingWebhookEnabled').checked
});
closeSettingsModal();
showToast('تنظیمات با موفقیت ذخیره شد', 'success');
WebhookManager.sendEvent('settings_updated', {
updatedFields: ['webhook']
});
});
// ============ Update Project UI ============
function updateProjectUI() {
document.title = projectData.projectTitle;
document.querySelector('.app-title').textContent = projectData.projectTitle;
document.querySelector('.app-subtitle').textContent = projectData.description;
document.querySelector('.app-footer').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>
`;
}
// ============ Keyboard Shortcuts ============
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;
}
}
});
// ============ Initialize ============
renderToolGroups();
updateProjectUI();
WebhookManager.updateStatusUI();
setTimeout(() => {
showToast(`${projectData.projectTitle} آماده استفاده است`, 'success');
}, 500);
</script>
</body>
</html>