Spaces:
Paused
Paused
<html lang="fa" dir="rtl"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>داشبورد مدیریتی حقوقی | سامانه هوشمند</title> | |
<link rel="preconnect" href="https://fonts.googleapis.com"> | |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
<link href="https://fonts.googleapis.com/css2?family=Vazirmatn:wght@200;300;400;500;600;700;800;900&display=swap" rel="stylesheet"> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.min.js"></script> | |
<script src="js/api-client.js"></script> | |
<script src="js/core.js"></script> | |
<script src="js/api-connection-test.js"></script> | |
<script src="js/file-upload-handler.js"></script> | |
<script src="js/document-crud.js"></script> | |
<script src="js/scraping-control.js"></script> | |
<style> | |
:root { | |
/* رنگبندی مدرن و هارمونیک */ | |
--text-primary: #0f172a; | |
--text-secondary: #475569; | |
--text-muted: #64748b; | |
--text-light: #ffffff; | |
/* پسزمینههای بهبود یافته */ | |
--body-bg: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 50%, #cbd5e1 100%); | |
--card-bg: rgba(255, 255, 255, 0.95); | |
--glass-bg: rgba(255, 255, 255, 0.9); | |
--glass-border: rgba(148, 163, 184, 0.2); | |
--sidebar-bg: linear-gradient(135deg, #1e293b 0%, #0f172a 100%); | |
/* گرادیانهای مدرن */ | |
--primary-gradient: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%); | |
--secondary-gradient: linear-gradient(135deg, #06b6d4 0%, #0891b2 100%); | |
--success-gradient: linear-gradient(135deg, #10b981 0%, #047857 100%); | |
--warning-gradient: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); | |
--danger-gradient: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); | |
/* سایههای ملایم */ | |
--shadow-xs: 0 1px 3px rgba(0, 0, 0, 0.05); | |
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08); | |
--shadow-md: 0 4px 15px rgba(0, 0, 0, 0.1); | |
--shadow-lg: 0 8px 25px rgba(0, 0, 0, 0.12); | |
--shadow-glow-primary: 0 0 20px rgba(59, 130, 246, 0.15); | |
--shadow-glow-success: 0 0 20px rgba(16, 185, 129, 0.15); | |
--shadow-glow-warning: 0 0 20px rgba(245, 158, 11, 0.15); | |
--shadow-glow-danger: 0 0 20px rgba(239, 68, 68, 0.15); | |
/* متغیرهای کامپکت */ | |
--sidebar-width: 260px; | |
--border-radius: 12px; | |
--border-radius-sm: 8px; | |
--border-radius-lg: 16px; | |
--transition-smooth: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); | |
--transition-fast: all 0.15s ease-in-out; | |
/* فونتهای کامپکت */ | |
--font-size-xs: 0.7rem; | |
--font-size-sm: 0.8rem; | |
--font-size-base: 0.9rem; | |
--font-size-lg: 1.1rem; | |
--font-size-xl: 1.25rem; | |
--font-size-2xl: 1.5rem; | |
} | |
/* ریست و تنظیمات پایه */ | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
} | |
html { | |
box-sizing: border-box; | |
} | |
body { | |
font-family: 'Vazirmatn', -apple-system, BlinkMacSystemFont, sans-serif; | |
background: var(--body-bg); | |
color: var(--text-primary); | |
line-height: 1.6; | |
overflow-x: hidden; | |
font-size: var(--font-size-base); | |
margin: 0; | |
padding: 0; | |
} | |
/* اسکرولبار مدرن */ | |
::-webkit-scrollbar { | |
inline-size: 6px; | |
block-size: 6px; | |
} | |
::-webkit-scrollbar-track { | |
background: rgba(0, 0, 0, 0.02); | |
border-radius: 10px; | |
} | |
::-webkit-scrollbar-thumb { | |
background: var(--primary-gradient); | |
border-radius: 10px; | |
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.3); | |
} | |
::-webkit-scrollbar-thumb:hover { | |
background: var(--secondary-gradient); | |
} | |
/* کلاسهای کمکی */ | |
.sr-only { | |
position: absolute; | |
inline-size: 1px; | |
block-size: 1px; | |
padding: 0; | |
margin: -1px; | |
overflow: hidden; | |
clip: rect(0, 0, 0, 0); | |
white-space: nowrap; | |
border: 0; | |
} | |
/* کانتینر اصلی */ | |
.dashboard-container { | |
display: flex; | |
min-block-size: 100vh; | |
inline-size: 100%; | |
position: relative; | |
overflow-x: hidden; | |
} | |
/* سایدبار کامپکت و بهبود یافته */ | |
.sidebar { | |
inline-size: var(--sidebar-width); | |
background: linear-gradient(135deg, | |
rgba(248, 250, 252, 0.98) 0%, | |
rgba(241, 245, 249, 0.95) 25%, | |
rgba(226, 232, 240, 0.98) 50%, | |
rgba(203, 213, 225, 0.95) 75%, | |
rgba(148, 163, 184, 0.1) 100%); | |
backdrop-filter: blur(25px); | |
-webkit-backdrop-filter: blur(25px); | |
padding: 1rem 0; | |
position: fixed; | |
block-size: 100vh; | |
inset-inline-end: 0; | |
inset-block-start: 0; | |
z-index: 1000; | |
overflow-y: auto; | |
overflow-x: hidden; | |
box-shadow: | |
0 0 0 1px rgba(59, 130, 246, 0.08), | |
-8px 0 32px rgba(59, 130, 246, 0.12), | |
inset 0 1px 0 rgba(255, 255, 255, 0.6); | |
border-inline-start: 1px solid rgba(59, 130, 246, 0.15); | |
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
} | |
.sidebar::before { | |
content: ''; | |
position: absolute; | |
inset-block-start: 0; | |
inset-inline-start: 0; | |
inset-inline-end: 0; | |
inset-block-end: 0; | |
background: | |
radial-gradient(circle at 20% 20%, rgba(59, 130, 246, 0.03) 0%, transparent 50%), | |
radial-gradient(circle at 80% 80%, rgba(16, 185, 129, 0.02) 0%, transparent 50%), | |
linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%); | |
pointer-events: none; | |
border-radius: inherit; | |
} | |
.sidebar.collapsed { | |
inline-size: 70px; | |
} | |
.sidebar.collapsed .logo-text, | |
.sidebar.collapsed .nav-title, | |
.sidebar.collapsed .nav-link span:not(.nav-badge), | |
.sidebar.collapsed .nav-badge, | |
.sidebar.collapsed .submenu-toggle { | |
opacity: 0; | |
visibility: hidden; | |
pointer-events: none; | |
transition: opacity 0.2s ease, visibility 0.2s ease; | |
} | |
.sidebar.collapsed .nav-link { | |
justify-content: center; | |
padding: 0.6rem; | |
position: relative; | |
} | |
.sidebar.collapsed .nav-link:hover::after { | |
content: attr(data-tooltip); | |
position: absolute; | |
inset-inline-start: 100%; | |
inset-block-start: 50%; | |
transform: translateY(-50%); | |
background: rgba(0, 0, 0, 0.8); | |
color: white; | |
padding: 0.5rem 0.8rem; | |
border-radius: 4px; | |
font-size: 0.8rem; | |
white-space: nowrap; | |
z-index: 10001; | |
margin-inline-start: 0.5rem; | |
opacity: 0; | |
animation: fadeIn 0.2s ease forwards; | |
} | |
@keyframes fadeIn { | |
to { opacity: 1; } | |
} | |
.sidebar.collapsed .nav-icon { | |
margin-inline-start: 0; | |
} | |
.sidebar.collapsed .submenu { | |
display: none; | |
} | |
.sidebar.collapsed .sidebar-header { | |
justify-content: center; | |
} | |
.sidebar.collapsed .logo { | |
gap: 0; | |
} | |
.sidebar.collapsed + .main-content { | |
margin-inline-end: 70px; | |
inline-size: calc(100% - 70px); | |
max-inline-size: calc(100% - 70px); | |
} | |
.sidebar-header { | |
padding: 0 1rem 1rem; | |
border-block-end: 1px solid rgba(59, 130, 246, 0.12); | |
margin-block-end: 1rem; | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
background: linear-gradient(135deg, | |
rgba(255, 255, 255, 0.4) 0%, | |
rgba(248, 250, 252, 0.2) 100%); | |
margin: 0 0.5rem 1rem; | |
border-radius: var(--border-radius); | |
backdrop-filter: blur(10px); | |
-webkit-backdrop-filter: blur(10px); | |
position: relative; | |
} | |
.sidebar-header::before { | |
content: ''; | |
position: absolute; | |
inset-block-start: 0; | |
inset-inline-start: 0; | |
inset-inline-end: 0; | |
inset-block-end: 0; | |
background: linear-gradient(135deg, | |
rgba(59, 130, 246, 0.05) 0%, | |
rgba(16, 185, 129, 0.03) 100%); | |
border-radius: inherit; | |
pointer-events: none; | |
} | |
.logo { | |
display: flex; | |
align-items: center; | |
gap: 0.6rem; | |
color: var(--text-primary); | |
transition: var(--transition-smooth); | |
position: relative; | |
z-index: 1; | |
} | |
.logo-icon { | |
inline-size: 2rem; | |
block-size: 2rem; | |
background: var(--primary-gradient); | |
border-radius: var(--border-radius-sm); | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
font-size: 1rem; | |
box-shadow: | |
var(--shadow-glow-primary), | |
inset 0 1px 0 rgba(255, 255, 255, 0.3); | |
position: relative; | |
} | |
.logo-icon::before { | |
content: ''; | |
position: absolute; | |
inset: 1px; | |
background: linear-gradient(135deg, | |
rgba(255, 255, 255, 0.2) 0%, | |
transparent 50%, | |
rgba(0, 0, 0, 0.1) 100%); | |
border-radius: inherit; | |
pointer-events: none; | |
} | |
.sidebar-toggle { | |
background: var(--primary-gradient); | |
border: none; | |
inline-size: 2rem; | |
block-size: 2rem; | |
border-radius: var(--border-radius-sm); | |
color: white; | |
cursor: pointer; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
transition: var(--transition-smooth); | |
box-shadow: | |
var(--shadow-sm), | |
inset 0 1px 0 rgba(255, 255, 255, 0.3); | |
position: relative; | |
z-index: 1; | |
} | |
.sidebar-toggle::before { | |
content: ''; | |
position: absolute; | |
inset: 1px; | |
background: linear-gradient(135deg, | |
rgba(255, 255, 255, 0.2) 0%, | |
transparent 50%); | |
border-radius: inherit; | |
pointer-events: none; | |
} | |
.sidebar-toggle:hover { | |
transform: scale(1.05); | |
box-shadow: | |
var(--shadow-glow-primary), | |
inset 0 1px 0 rgba(255, 255, 255, 0.4); | |
} | |
.logo-text { | |
font-size: var(--font-size-lg); | |
font-weight: 700; | |
background: var(--primary-gradient); | |
-webkit-background-clip: text; | |
-webkit-text-fill-color: transparent; | |
transition: var(--transition-smooth); | |
} | |
/* منوی کامپکت */ | |
.nav-section { | |
margin-block-end: 1rem; | |
} | |
.nav-title { | |
padding: 0 1rem 0.4rem; | |
font-size: var(--font-size-xs); | |
font-weight: 600; | |
text-transform: uppercase; | |
letter-spacing: 0.5px; | |
color: var(--text-secondary); | |
transition: var(--transition-smooth); | |
background: linear-gradient(90deg, | |
var(--text-secondary) 0%, | |
var(--primary-gradient) 100%); | |
-webkit-background-clip: text; | |
-webkit-text-fill-color: transparent; | |
position: relative; | |
} | |
.nav-title::after { | |
content: ''; | |
position: absolute; | |
inset-block-end: 0; | |
inset-inline-end: 1rem; | |
inset-inline-start: 1rem; | |
block-size: 1px; | |
background: linear-gradient(90deg, | |
transparent 0%, | |
rgba(59, 130, 246, 0.3) 50%, | |
transparent 100%); | |
} | |
.nav-menu { | |
list-style: none; | |
} | |
.nav-item { | |
margin: 0.15rem 0.5rem; | |
} | |
.nav-link { | |
display: flex; | |
align-items: center; | |
padding: 0.6rem 0.8rem; | |
color: var(--text-primary); | |
text-decoration: none; | |
border-radius: var(--border-radius-sm); | |
transition: var(--transition-smooth); | |
position: relative; | |
font-weight: 500; | |
font-size: var(--font-size-sm); | |
cursor: pointer; | |
border: 1px solid transparent; | |
background: transparent; | |
} | |
.nav-link::before { | |
content: ''; | |
position: absolute; | |
inset: 0; | |
background: linear-gradient(135deg, | |
rgba(59, 130, 246, 0.0) 0%, | |
rgba(59, 130, 246, 0.0) 100%); | |
border-radius: inherit; | |
transition: var(--transition-smooth); | |
opacity: 0; | |
} | |
.nav-link:hover { | |
color: var(--text-primary); | |
transform: translateX(-2px); | |
border-color: rgba(59, 130, 246, 0.15); | |
box-shadow: | |
0 2px 8px rgba(59, 130, 246, 0.08), | |
inset 0 1px 0 rgba(255, 255, 255, 0.1); | |
} | |
.nav-link:hover::before { | |
background: linear-gradient(135deg, | |
rgba(59, 130, 246, 0.08) 0%, | |
rgba(16, 185, 129, 0.04) 100%); | |
opacity: 1; | |
} | |
.nav-link.active { | |
background: var(--primary-gradient); | |
color: var(--text-light); | |
box-shadow: | |
var(--shadow-glow-primary), | |
inset 0 1px 0 rgba(255, 255, 255, 0.3), | |
0 4px 12px rgba(59, 130, 246, 0.4); | |
border-color: rgba(59, 130, 246, 0.5); | |
position: relative; | |
} | |
.nav-link.active::before { | |
background: linear-gradient(135deg, | |
rgba(255, 255, 255, 0.1) 0%, | |
transparent 50%, | |
rgba(0, 0, 0, 0.05) 100%); | |
opacity: 1; | |
} | |
.nav-link.active::after { | |
content: ''; | |
position: absolute; | |
inset-inline-end: -0.5rem; | |
inset-block-start: 50%; | |
transform: translateY(-50%); | |
inline-size: 3px; | |
block-size: 1.5rem; | |
background: var(--primary-gradient); | |
border-radius: 2px; | |
box-shadow: | |
0 0 8px rgba(59, 130, 246, 0.6), | |
inset 0 1px 0 rgba(255, 255, 255, 0.3); | |
} | |
.nav-icon { | |
margin-inline-start: 0.6rem; | |
inline-size: 1rem; | |
text-align: center; | |
font-size: 0.9rem; | |
opacity: 0.9; | |
transition: var(--transition-smooth); | |
} | |
.submenu-toggle { | |
background: none; | |
border: none; | |
color: var(--text-secondary); | |
padding: 0.2rem; | |
cursor: pointer; | |
margin-inline-end: auto; | |
transition: var(--transition-fast); | |
font-size: 0.8rem; | |
} | |
.submenu-toggle:hover { | |
color: var(--text-primary); | |
} | |
.submenu-toggle.open { | |
transform: rotate(90deg); | |
} | |
.submenu { | |
max-block-size: 0; | |
overflow: hidden; | |
transition: max-height 0.25s ease-out; | |
margin-block-start: 0.15rem; | |
} | |
.submenu.open { | |
max-block-size: 800px; | |
} | |
.submenu .nav-link { | |
padding: 0.45rem 0.6rem 0.45rem 2rem; | |
font-size: var(--font-size-xs); | |
color: var(--text-secondary); | |
margin: 0.1rem 0; | |
background: rgba(255, 255, 255, 0.3); | |
backdrop-filter: blur(5px); | |
-webkit-backdrop-filter: blur(5px); | |
border: 1px solid rgba(59, 130, 246, 0.08); | |
position: relative; | |
} | |
.submenu .nav-link::before { | |
content: ''; | |
position: absolute; | |
inset-inline-end: 1.2rem; | |
inset-block-start: 50%; | |
transform: translateY(-50%); | |
inline-size: 3px; | |
block-size: 3px; | |
background: var(--primary-gradient); | |
border-radius: 50%; | |
opacity: 0.6; | |
} | |
.submenu .nav-link:hover { | |
color: var(--text-primary); | |
background: rgba(59, 130, 246, 0.08); | |
border-color: rgba(59, 130, 246, 0.15); | |
box-shadow: | |
0 2px 8px rgba(59, 130, 246, 0.1), | |
inset 0 1px 0 rgba(255, 255, 255, 0.2); | |
} | |
.submenu .nav-link:hover::before { | |
opacity: 1; | |
transform: translateY(-50%) scale(1.2); | |
} | |
.nav-badge { | |
background: var(--danger-gradient); | |
color: white; | |
padding: 0.15rem 0.4rem; | |
border-radius: 10px; | |
font-size: var(--font-size-xs); | |
font-weight: 600; | |
margin-inline-end: auto; | |
min-inline-size: 1.2rem; | |
text-align: center; | |
box-shadow: | |
0 2px 4px rgba(239, 68, 68, 0.3), | |
inset 0 1px 0 rgba(255, 255, 255, 0.3); | |
position: relative; | |
} | |
.nav-badge::before { | |
content: ''; | |
position: absolute; | |
inset: 1px; | |
background: linear-gradient(135deg, | |
rgba(255, 255, 255, 0.2) 0%, | |
transparent 70%); | |
border-radius: inherit; | |
pointer-events: none; | |
} | |
/* محتوای اصلی */ | |
.main-content { | |
flex: 1; | |
margin-inline-end: var(--sidebar-width); | |
padding: 1rem; | |
min-block-size: 100vh; | |
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
inline-size: calc(100% - var(--sidebar-width)); | |
max-inline-size: calc(100% - var(--sidebar-width)); | |
box-sizing: border-box; | |
} | |
.main-content.sidebar-collapsed { | |
margin-inline-end: 70px; | |
inline-size: calc(100% - 70px); | |
max-inline-size: calc(100% - 70px); | |
} | |
/* هدر کامپکت */ | |
.dashboard-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-block-end: 1.2rem; | |
padding: 0.8rem 0; | |
} | |
.dashboard-title { | |
font-size: var(--font-size-2xl); | |
font-weight: 800; | |
background: var(--primary-gradient); | |
-webkit-background-clip: text; | |
-webkit-text-fill-color: transparent; | |
display: flex; | |
align-items: center; | |
gap: 0.6rem; | |
position: relative; | |
} | |
.dashboard-title::after { | |
content: ''; | |
position: absolute; | |
inset-block-end: -0.3rem; | |
inset-inline-end: 0; | |
inline-size: 2.5rem; | |
block-size: 2px; | |
background: var(--primary-gradient); | |
border-radius: 2px; | |
} | |
.title-icon { | |
background: var(--primary-gradient); | |
-webkit-background-clip: text; | |
-webkit-text-fill-color: transparent; | |
} | |
.header-actions { | |
display: flex; | |
align-items: center; | |
gap: 0.8rem; | |
} | |
.search-container { | |
position: relative; | |
} | |
.search-input { | |
inline-size: 280px; | |
padding: 0.6rem 1rem 0.6rem 2.2rem; | |
border: none; | |
border-radius: 20px; | |
background: var(--glass-bg); | |
backdrop-filter: blur(10px); | |
-webkit-backdrop-filter: blur(10px); | |
box-shadow: var(--shadow-sm); | |
font-family: inherit; | |
font-size: var(--font-size-sm); | |
color: var(--text-primary); | |
transition: var(--transition-smooth); | |
border: 1px solid var(--glass-border); | |
} | |
.search-input::placeholder { | |
color: var(--text-secondary); | |
} | |
.search-input:focus { | |
outline: none; | |
box-shadow: var(--shadow-glow-primary); | |
background: var(--card-bg); | |
border-color: rgba(59, 130, 246, 0.3); | |
} | |
.search-icon { | |
position: absolute; | |
inset-inline-end: 0.8rem; | |
inset-block-start: 50%; | |
transform: translateY(-50%); | |
color: var(--text-secondary); | |
font-size: 0.9rem; | |
} | |
.user-profile { | |
display: flex; | |
align-items: center; | |
gap: 0.6rem; | |
padding: 0.4rem 0.8rem; | |
background: var(--glass-bg); | |
backdrop-filter: blur(10px); | |
-webkit-backdrop-filter: blur(10px); | |
border-radius: 18px; | |
box-shadow: var(--shadow-sm); | |
cursor: pointer; | |
transition: var(--transition-smooth); | |
border: 1px solid var(--glass-border); | |
} | |
.user-profile:hover { | |
box-shadow: var(--shadow-md); | |
transform: translateY(-1px); | |
} | |
.user-avatar { | |
inline-size: 1.8rem; | |
block-size: 1.8rem; | |
border-radius: 50%; | |
background: var(--primary-gradient); | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
color: white; | |
font-weight: 600; | |
font-size: var(--font-size-sm); | |
box-shadow: | |
0 4px 12px rgba(59, 130, 246, 0.3), | |
inset 0 1px 0 rgba(255, 255, 255, 0.3), | |
inset 0 -1px 0 rgba(0, 0, 0, 0.1); | |
position: relative; | |
} | |
.user-avatar::before { | |
content: ''; | |
position: absolute; | |
inset: 1px; | |
background: linear-gradient(135deg, | |
rgba(255, 255, 255, 0.2) 0%, | |
transparent 50%, | |
rgba(0, 0, 0, 0.05) 100%); | |
border-radius: inherit; | |
pointer-events: none; | |
} | |
.user-info { | |
display: flex; | |
flex-direction: column; | |
} | |
.user-name { | |
font-weight: 600; | |
color: var(--text-primary); | |
font-size: var(--font-size-sm); | |
} | |
.user-role { | |
font-size: var(--font-size-xs); | |
color: var(--text-secondary); | |
} | |
/* کارتهای آمار کامپکت */ | |
.stats-grid { | |
display: grid; | |
grid-template-columns: repeat(4, 1fr); | |
gap: 1rem; | |
margin-block-end: 1.2rem; | |
} | |
.stat-card { | |
background: var(--card-bg); | |
backdrop-filter: blur(10px); | |
-webkit-backdrop-filter: blur(10px); | |
border-radius: var(--border-radius); | |
padding: 1.2rem; | |
box-shadow: | |
0 4px 20px rgba(0, 0, 0, 0.08), | |
0 2px 8px rgba(0, 0, 0, 0.06), | |
0 1px 3px rgba(0, 0, 0, 0.12), | |
inset 0 1px 0 rgba(255, 255, 255, 0.6); | |
border: 1px solid rgba(255, 255, 255, 0.3); | |
position: relative; | |
overflow: hidden; | |
transition: var(--transition-smooth); | |
min-block-size: 130px; | |
} | |
.stat-card::before { | |
content: ''; | |
position: absolute; | |
inset-block-start: 0; | |
inset-inline-start: 0; | |
inset-inline-end: 0; | |
block-size: 4px; | |
background: var(--primary-gradient); | |
} | |
.stat-card::after { | |
content: ''; | |
position: absolute; | |
inset-block-start: 0; | |
inset-inline-start: 0; | |
inset-inline-end: 0; | |
inset-block-end: 0; | |
background: linear-gradient(135deg, | |
rgba(255, 255, 255, 0.1) 0%, | |
rgba(255, 255, 255, 0.05) 50%, | |
transparent 100%); | |
pointer-events: none; | |
} | |
.stat-card.primary::before { background: var(--primary-gradient); } | |
.stat-card.success::before { background: var(--success-gradient); } | |
.stat-card.danger::before { background: var(--danger-gradient); } | |
.stat-card.warning::before { background: var(--warning-gradient); } | |
.stat-card:hover { | |
transform: translateY(-6px) scale(1.02); | |
box-shadow: | |
0 12px 35px rgba(0, 0, 0, 0.15), | |
0 6px 20px rgba(0, 0, 0, 0.1), | |
0 2px 8px rgba(0, 0, 0, 0.12), | |
inset 0 1px 0 rgba(255, 255, 255, 0.8); | |
} | |
.stat-card.primary:hover { | |
box-shadow: | |
0 12px 35px rgba(59, 130, 246, 0.25), | |
0 6px 20px rgba(59, 130, 246, 0.15), | |
0 2px 8px rgba(59, 130, 246, 0.1), | |
inset 0 1px 0 rgba(255, 255, 255, 0.8); | |
} | |
.stat-card.success:hover { | |
box-shadow: | |
0 12px 35px rgba(16, 185, 129, 0.25), | |
0 6px 20px rgba(16, 185, 129, 0.15), | |
0 2px 8px rgba(16, 185, 129, 0.1), | |
inset 0 1px 0 rgba(255, 255, 255, 0.8); | |
} | |
.stat-card.danger:hover { | |
box-shadow: | |
0 12px 35px rgba(239, 68, 68, 0.25), | |
0 6px 20px rgba(239, 68, 68, 0.15), | |
0 2px 8px rgba(239, 68, 68, 0.1), | |
inset 0 1px 0 rgba(255, 255, 255, 0.8); | |
} | |
.stat-card.warning:hover { | |
box-shadow: | |
0 12px 35px rgba(245, 158, 11, 0.25), | |
0 6px 20px rgba(245, 158, 11, 0.15), | |
0 2px 8px rgba(245, 158, 11, 0.1), | |
inset 0 1px 0 rgba(255, 255, 255, 0.8); | |
} | |
.stat-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: flex-start; | |
margin-block-end: 0.8rem; | |
position: relative; | |
z-index: 1; | |
} | |
.stat-icon { | |
inline-size: 2.2rem; | |
block-size: 2.2rem; | |
border-radius: var(--border-radius-sm); | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
font-size: 1.1rem; | |
box-shadow: | |
0 4px 12px rgba(0, 0, 0, 0.15), | |
inset 0 1px 0 rgba(255, 255, 255, 0.3), | |
inset 0 -1px 0 rgba(0, 0, 0, 0.1); | |
opacity: 0.95; | |
transition: var(--transition-smooth); | |
position: relative; | |
} | |
.stat-icon::before { | |
content: ''; | |
position: absolute; | |
inset: 1px; | |
background: linear-gradient(135deg, | |
rgba(255, 255, 255, 0.2) 0%, | |
transparent 50%, | |
rgba(0, 0, 0, 0.05) 100%); | |
border-radius: inherit; | |
pointer-events: none; | |
} | |
.stat-card:hover .stat-icon { | |
transform: scale(1.08); | |
box-shadow: | |
0 6px 16px rgba(0, 0, 0, 0.2), | |
inset 0 1px 0 rgba(255, 255, 255, 0.4), | |
inset 0 -1px 0 rgba(0, 0, 0, 0.15); | |
} | |
.stat-icon.primary { background: var(--primary-gradient); color: white; } | |
.stat-icon.success { background: var(--success-gradient); color: white; } | |
.stat-icon.danger { background: var(--danger-gradient); color: white; } | |
.stat-icon.warning { background: var(--warning-gradient); color: white; } | |
.stat-content { | |
flex: 1; | |
} | |
.stat-title { | |
font-size: var(--font-size-xs); | |
color: var(--text-secondary); | |
font-weight: 600; | |
margin-block-end: 0.3rem; | |
line-height: 1.3; | |
} | |
.stat-value { | |
font-size: var(--font-size-xl); | |
font-weight: 800; | |
color: var(--text-primary); | |
line-height: 1; | |
margin-block-end: 0.3rem; | |
} | |
.stat-extra { | |
font-size: var(--font-size-xs); | |
color: var(--text-muted); | |
margin-block-end: 0.3rem; | |
} | |
.stat-change { | |
display: flex; | |
align-items: center; | |
gap: 0.25rem; | |
font-size: var(--font-size-xs); | |
font-weight: 700; | |
} | |
.stat-change.positive { color: #059669; } | |
.stat-change.negative { color: #dc2626; } | |
/* نمودارها */ | |
.charts-section { | |
display: grid; | |
grid-template-columns: 2fr 1fr; | |
gap: 1.5rem; | |
margin-block-end: 1.5rem; | |
} | |
.chart-card { | |
background: var(--card-bg); | |
backdrop-filter: blur(10px); | |
-webkit-backdrop-filter: blur(10px); | |
border-radius: var(--border-radius); | |
padding: 1.5rem; | |
box-shadow: | |
0 8px 25px rgba(0, 0, 0, 0.1), | |
0 4px 12px rgba(0, 0, 0, 0.08), | |
0 2px 6px rgba(0, 0, 0, 0.12), | |
inset 0 1px 0 rgba(255, 255, 255, 0.6); | |
border: 1px solid rgba(255, 255, 255, 0.3); | |
transition: var(--transition-smooth); | |
position: relative; | |
} | |
.chart-card::after { | |
content: ''; | |
position: absolute; | |
inset-block-start: 0; | |
inset-inline-start: 0; | |
inset-inline-end: 0; | |
inset-block-end: 0; | |
background: linear-gradient(135deg, | |
rgba(255, 255, 255, 0.08) 0%, | |
rgba(255, 255, 255, 0.03) 50%, | |
transparent 100%); | |
pointer-events: none; | |
border-radius: inherit; | |
} | |
.chart-card:hover { | |
transform: translateY(-4px); | |
box-shadow: | |
0 12px 35px rgba(0, 0, 0, 0.15), | |
0 6px 20px rgba(0, 0, 0, 0.1), | |
0 3px 10px rgba(0, 0, 0, 0.12), | |
inset 0 1px 0 rgba(255, 255, 255, 0.8); | |
} | |
.chart-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-block-end: 1.2rem; | |
} | |
.chart-title { | |
font-size: var(--font-size-lg); | |
font-weight: 700; | |
color: var(--text-primary); | |
} | |
.chart-filters { | |
display: flex; | |
gap: 0.3rem; | |
} | |
.chart-filter { | |
padding: 0.3rem 0.8rem; | |
border: none; | |
border-radius: 12px; | |
background: rgba(59, 130, 246, 0.08); | |
color: var(--text-secondary); | |
font-family: inherit; | |
font-size: var(--font-size-xs); | |
font-weight: 500; | |
cursor: pointer; | |
transition: var(--transition-fast); | |
} | |
.chart-filter:hover { | |
background: rgba(59, 130, 246, 0.12); | |
} | |
.chart-filter.active { | |
background: var(--primary-gradient); | |
color: white; | |
box-shadow: var(--shadow-glow-primary); | |
} | |
.chart-container { | |
block-size: 280px; | |
position: relative; | |
} | |
.chart-placeholder { | |
block-size: 100%; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
flex-direction: column; | |
color: var(--text-muted); | |
background: rgba(0, 0, 0, 0.02); | |
border-radius: var(--border-radius-sm); | |
border: 2px dashed rgba(0, 0, 0, 0.1); | |
} | |
.chart-placeholder i { | |
font-size: 3rem; | |
margin-block-end: 1rem; | |
opacity: 0.3; | |
} | |
/* Toast Notifications */ | |
.toast-container { | |
position: fixed; | |
inset-block-start: 1rem; | |
inset-inline-start: 1rem; | |
z-index: 10001; | |
display: flex; | |
flex-direction: column; | |
gap: 0.5rem; | |
} | |
.toast { | |
background: var(--card-bg); | |
border-radius: var(--border-radius-sm); | |
padding: 1rem 1.5rem; | |
box-shadow: var(--shadow-lg); | |
border-inline-start: 4px solid; | |
display: flex; | |
align-items: center; | |
gap: 0.8rem; | |
min-inline-size: 300px; | |
transform: translateX(-100%); | |
transition: all 0.3s ease; | |
} | |
.toast.show { | |
transform: translateX(0); | |
} | |
.toast.success { | |
border-inline-start-color: #10b981; | |
} | |
.toast.error { | |
border-inline-start-color: #ef4444; | |
} | |
.toast.warning { | |
border-inline-start-color: #f59e0b; | |
} | |
.toast.info { | |
border-inline-start-color: #3b82f6; | |
} | |
.toast-icon { | |
font-size: 1.2rem; | |
} | |
.toast.success .toast-icon { | |
color: #10b981; | |
} | |
.toast.error .toast-icon { | |
color: #ef4444; | |
} | |
.toast.warning .toast-icon { | |
color: #f59e0b; | |
} | |
.toast.info .toast-icon { | |
color: #3b82f6; | |
} | |
.toast-content { | |
flex: 1; | |
} | |
.toast-title { | |
font-weight: 600; | |
font-size: var(--font-size-sm); | |
margin-block-end: 0.2rem; | |
} | |
.toast-message { | |
font-size: var(--font-size-xs); | |
color: var(--text-secondary); | |
} | |
.toast-close { | |
background: none; | |
border: none; | |
color: var(--text-secondary); | |
cursor: pointer; | |
font-size: 1rem; | |
transition: var(--transition-fast); | |
} | |
.toast-close:hover { | |
color: var(--text-primary); | |
} | |
/* Connection Status */ | |
.connection-status { | |
position: fixed; | |
inset-block-end: 1rem; | |
inset-inline-start: 1rem; | |
background: var(--card-bg); | |
border-radius: var(--border-radius-sm); | |
padding: 0.5rem 1rem; | |
box-shadow: var(--shadow-sm); | |
display: flex; | |
align-items: center; | |
gap: 0.5rem; | |
font-size: var(--font-size-xs); | |
border-inline-start: 3px solid; | |
z-index: 1000; | |
} | |
.connection-status.online { | |
border-inline-start-color: #10b981; | |
color: #047857; | |
} | |
.connection-status.offline { | |
border-inline-start-color: #ef4444; | |
color: #b91c1c; | |
} | |
.status-indicator { | |
inline-size: 8px; | |
block-size: 8px; | |
border-radius: 50%; | |
} | |
.connection-status.online .status-indicator { | |
background: #10b981; | |
animation: pulse 2s infinite; | |
} | |
.connection-status.offline .status-indicator { | |
background: #ef4444; | |
} | |
@keyframes pulse { | |
0%, 100% { opacity: 1; } | |
50% { opacity: 0.5; } | |
} | |
/* دکمه منوی موبایل */ | |
.mobile-menu-toggle { | |
display: none; | |
background: var(--glass-bg); | |
border: 1px solid var(--glass-border); | |
padding: 0.5rem; | |
border-radius: var(--border-radius-sm); | |
color: var(--text-primary); | |
font-size: 1rem; | |
cursor: pointer; | |
transition: var(--transition-fast); | |
} | |
.mobile-menu-toggle:hover { | |
background: var(--primary-gradient); | |
color: white; | |
} | |
/* انیمیشنها */ | |
@keyframes fadeInUp { | |
from { | |
opacity: 0; | |
transform: translateY(15px); | |
} | |
to { | |
opacity: 1; | |
transform: translateY(0); | |
} | |
} | |
.animate-fade-in { | |
animation: fadeInUp 0.4s ease-out; | |
} | |
/* واکنشگرایی */ | |
@media (max-inline-size: 1200px) { | |
.charts-section { | |
grid-template-columns: 1fr; | |
} | |
.search-input { | |
inline-size: 220px; | |
} | |
.stats-grid { | |
grid-template-columns: repeat(2, 1fr); | |
} | |
} | |
@media (max-inline-size: 992px) { | |
.mobile-menu-toggle { | |
display: block; | |
} | |
.sidebar { | |
transform: translateX(100%); | |
position: fixed; | |
z-index: 10000; | |
} | |
.sidebar.open { | |
transform: translateX(0); | |
} | |
.main-content { | |
margin-inline-end: 0; | |
inline-size: 100%; | |
max-inline-size: 100%; | |
padding: 1rem; | |
} | |
.main-content.sidebar-collapsed { | |
margin-inline-end: 0; | |
inline-size: 100%; | |
max-inline-size: 100%; | |
} | |
.dashboard-header { | |
flex-direction: column; | |
align-items: flex-start; | |
gap: 0.8rem; | |
} | |
.header-actions { | |
inline-size: 100%; | |
justify-content: space-between; | |
flex-direction: column; | |
gap: 0.8rem; | |
} | |
.search-container { | |
inline-size: 100%; | |
} | |
.search-input { | |
inline-size: 100%; | |
} | |
.sidebar-toggle { | |
display: none; | |
} | |
} | |
@media (max-inline-size: 768px) { | |
.main-content { | |
padding: 0.8rem; | |
inline-size: 100%; | |
margin-inline-end: 0; | |
max-inline-size: 100%; | |
} | |
.stats-grid { | |
grid-template-columns: 1fr; | |
gap: 0.6rem; | |
} | |
.stat-card { | |
min-block-size: 100px; | |
padding: 0.8rem; | |
} | |
.stat-value { | |
font-size: var(--font-size-lg); | |
} | |
.chart-container { | |
block-size: 220px; | |
} | |
} | |
@media (max-inline-size: 480px) { | |
.dashboard-title { | |
font-size: var(--font-size-xl); | |
} | |
.stat-value { | |
font-size: var(--font-size-base); | |
} | |
.chart-card { | |
padding: 0.8rem; | |
} | |
.main-content { | |
padding: 0.5rem; | |
inline-size: 100%; | |
} | |
} | |
/* File Upload Section Styles */ | |
.upload-section { | |
background: var(--card-bg); | |
border-radius: var(--border-radius); | |
padding: 1.5rem; | |
margin-block-end: 2rem; | |
box-shadow: var(--shadow-sm); | |
} | |
.upload-drop-zone { | |
border: 2px dashed #cbd5e1; | |
border-radius: var(--border-radius); | |
padding: 2rem; | |
text-align: center; | |
transition: var(--transition-smooth); | |
cursor: pointer; | |
} | |
.upload-drop-zone:hover, | |
.upload-drop-zone.drag-over { | |
border-color: #3b82f6; | |
background: rgba(59, 130, 246, 0.05); | |
} | |
.upload-content { | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
gap: 1rem; | |
} | |
.upload-icon { | |
font-size: 3rem; | |
color: #64748b; | |
} | |
.upload-btn { | |
background: var(--primary-gradient); | |
color: white; | |
border: none; | |
padding: 0.8rem 1.5rem; | |
border-radius: var(--border-radius); | |
cursor: pointer; | |
font-weight: 500; | |
transition: var(--transition-smooth); | |
} | |
.upload-btn:hover { | |
transform: translateY(-2px); | |
box-shadow: var(--shadow-glow-primary); | |
} | |
.upload-queue { | |
margin-block-start: 1.5rem; | |
padding: 1rem; | |
background: #f8fafc; | |
border-radius: var(--border-radius); | |
} | |
.queue-item { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
padding: 0.8rem; | |
background: white; | |
border-radius: var(--border-radius-sm); | |
margin-block-end: 0.5rem; | |
box-shadow: var(--shadow-xs); | |
} | |
.file-info { | |
display: flex; | |
flex-direction: column; | |
gap: 0.3rem; | |
} | |
.file-name { | |
font-weight: 500; | |
color: var(--text-primary); | |
} | |
.file-size { | |
font-size: var(--font-size-sm); | |
color: var(--text-muted); | |
} | |
.remove-file { | |
background: #ef4444; | |
color: white; | |
border: none; | |
padding: 0.3rem 0.6rem; | |
border-radius: 50%; | |
cursor: pointer; | |
font-size: 0.8rem; | |
} | |
.upload-progress { | |
margin-block-start: 1rem; | |
padding: 1rem; | |
background: #f8fafc; | |
border-radius: var(--border-radius); | |
} | |
.progress-bar { | |
inline-size: 100%; | |
block-size: 8px; | |
background: #e2e8f0; | |
border-radius: 4px; | |
overflow: hidden; | |
} | |
.progress-fill { | |
block-size: 100%; | |
background: var(--primary-gradient); | |
transition: width 0.3s ease; | |
} | |
.progress-text { | |
margin-block-start: 0.5rem; | |
text-align: center; | |
color: var(--text-secondary); | |
} | |
.upload-actions { | |
margin-block-start: 1rem; | |
text-align: center; | |
} | |
.ocr-results { | |
margin-block-start: 2rem; | |
padding: 1rem; | |
background: #f8fafc; | |
border-radius: var(--border-radius); | |
} | |
.ocr-result { | |
background: white; | |
padding: 1rem; | |
border-radius: var(--border-radius-sm); | |
margin-block-end: 1rem; | |
box-shadow: var(--shadow-xs); | |
} | |
.extracted-text { | |
background: #f1f5f9; | |
padding: 0.8rem; | |
border-radius: var(--border-radius-sm); | |
margin-block-start: 0.5rem; | |
font-family: monospace; | |
white-space: pre-wrap; | |
} | |
/* Document Management Section Styles */ | |
.documents-section { | |
background: var(--card-bg); | |
border-radius: var(--border-radius); | |
padding: 1.5rem; | |
margin-block-end: 2rem; | |
box-shadow: var(--shadow-sm); | |
} | |
.section-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-block-end: 1.5rem; | |
} | |
.section-title { | |
display: flex; | |
align-items: center; | |
gap: 0.8rem; | |
font-size: var(--font-size-xl); | |
color: var(--text-primary); | |
} | |
.section-actions { | |
display: flex; | |
gap: 0.8rem; | |
} | |
.documents-filters { | |
display: grid; | |
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
gap: 1rem; | |
margin-block-end: 1.5rem; | |
padding: 1rem; | |
background: #f8fafc; | |
border-radius: var(--border-radius); | |
} | |
.filter-group { | |
display: flex; | |
flex-direction: column; | |
gap: 0.5rem; | |
} | |
.search-input, | |
.filter-select, | |
.date-input { | |
padding: 0.6rem; | |
border: 1px solid #cbd5e1; | |
border-radius: var(--border-radius-sm); | |
font-size: var(--font-size-base); | |
transition: var(--transition-smooth); | |
} | |
.search-input:focus, | |
.filter-select:focus, | |
.date-input:focus { | |
outline: none; | |
border-color: #3b82f6; | |
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); | |
} | |
.documents-list { | |
display: flex; | |
flex-direction: column; | |
gap: 1rem; | |
} | |
.document-item { | |
background: white; | |
border-radius: var(--border-radius); | |
padding: 1rem; | |
box-shadow: var(--shadow-xs); | |
transition: var(--transition-smooth); | |
} | |
.document-item:hover { | |
box-shadow: var(--shadow-md); | |
transform: translateY(-2px); | |
} | |
.document-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-block-end: 0.8rem; | |
} | |
.document-title { | |
font-size: var(--font-size-lg); | |
color: var(--text-primary); | |
margin: 0; | |
} | |
.document-actions { | |
display: flex; | |
gap: 0.5rem; | |
} | |
.btn-edit, | |
.btn-delete { | |
background: none; | |
border: none; | |
padding: 0.4rem; | |
border-radius: var(--border-radius-sm); | |
cursor: pointer; | |
transition: var(--transition-smooth); | |
} | |
.btn-edit { | |
color: #3b82f6; | |
} | |
.btn-delete { | |
color: #ef4444; | |
} | |
.btn-edit:hover { | |
background: rgba(59, 130, 246, 0.1); | |
} | |
.btn-delete:hover { | |
background: rgba(239, 68, 68, 0.1); | |
} | |
.document-details { | |
display: flex; | |
flex-direction: column; | |
gap: 0.8rem; | |
} | |
.document-info { | |
display: flex; | |
gap: 1rem; | |
flex-wrap: wrap; | |
} | |
.document-status, | |
.document-quality, | |
.document-date { | |
padding: 0.3rem 0.6rem; | |
border-radius: var(--border-radius-sm); | |
font-size: var(--font-size-sm); | |
} | |
.status-pending { | |
background: #fef3c7; | |
color: #92400e; | |
} | |
.status-processing { | |
background: #dbeafe; | |
color: #1e40af; | |
} | |
.status-completed { | |
background: #d1fae5; | |
color: #065f46; | |
} | |
.status-error { | |
background: #fee2e2; | |
color: #991b1b; | |
} | |
.document-description { | |
color: var(--text-secondary); | |
margin: 0; | |
} | |
/* Scraping Control Section Styles */ | |
.scraping-section { | |
background: var(--card-bg); | |
border-radius: var(--border-radius); | |
padding: 1.5rem; | |
margin-block-end: 2rem; | |
box-shadow: var(--shadow-sm); | |
} | |
.scraping-controls { | |
display: grid; | |
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); | |
gap: 1rem; | |
margin-block-end: 1.5rem; | |
padding: 1rem; | |
background: #f8fafc; | |
border-radius: var(--border-radius); | |
} | |
.control-group { | |
display: flex; | |
flex-direction: column; | |
gap: 0.5rem; | |
} | |
.control-group label { | |
font-weight: 500; | |
color: var(--text-primary); | |
} | |
.form-input, | |
.form-select { | |
padding: 0.6rem; | |
border: 1px solid #cbd5e1; | |
border-radius: var(--border-radius-sm); | |
font-size: var(--font-size-base); | |
transition: var(--transition-smooth); | |
} | |
.form-input:focus, | |
.form-select:focus { | |
outline: none; | |
border-color: #3b82f6; | |
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); | |
} | |
.scraping-actions { | |
display: flex; | |
gap: 1rem; | |
margin-block-end: 1.5rem; | |
flex-wrap: wrap; | |
} | |
.scraping-status { | |
padding: 1rem; | |
background: #f8fafc; | |
border-radius: var(--border-radius); | |
margin-block-end: 1.5rem; | |
} | |
.status-info { | |
display: flex; | |
align-items: center; | |
gap: 0.8rem; | |
margin-block-end: 1rem; | |
} | |
.status-label { | |
font-weight: 500; | |
color: var(--text-primary); | |
} | |
.status-value { | |
padding: 0.3rem 0.8rem; | |
border-radius: var(--border-radius-sm); | |
font-size: var(--font-size-sm); | |
font-weight: 500; | |
} | |
.status-idle { | |
background: #f1f5f9; | |
color: #64748b; | |
} | |
.status-running { | |
background: #dbeafe; | |
color: #1e40af; | |
} | |
.status-completed { | |
background: #d1fae5; | |
color: #065f46; | |
} | |
.status-failed { | |
background: #fee2e2; | |
color: #991b1b; | |
} | |
.status-stopped { | |
background: #fef3c7; | |
color: #92400e; | |
} | |
.scraping-stats { | |
display: grid; | |
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); | |
gap: 1rem; | |
margin-block-start: 1rem; | |
} | |
.stat-item { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
padding: 0.8rem; | |
background: white; | |
border-radius: var(--border-radius-sm); | |
box-shadow: var(--shadow-xs); | |
} | |
.stat-label { | |
font-size: var(--font-size-sm); | |
color: var(--text-secondary); | |
} | |
.stat-value { | |
font-weight: 600; | |
color: var(--text-primary); | |
} | |
.scraping-results { | |
margin-block-end: 1.5rem; | |
} | |
.scraping-item { | |
background: white; | |
border-radius: var(--border-radius); | |
padding: 1rem; | |
margin-block-end: 1rem; | |
box-shadow: var(--shadow-xs); | |
transition: var(--transition-smooth); | |
} | |
.scraping-item:hover { | |
box-shadow: var(--shadow-md); | |
} | |
.item-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-block-end: 0.8rem; | |
} | |
.item-title { | |
font-size: var(--font-size-lg); | |
color: var(--text-primary); | |
margin: 0; | |
} | |
.item-rating { | |
background: #fef3c7; | |
color: #92400e; | |
padding: 0.3rem 0.6rem; | |
border-radius: var(--border-radius-sm); | |
font-size: var(--font-size-sm); | |
font-weight: 500; | |
} | |
.item-content { | |
display: flex; | |
flex-direction: column; | |
gap: 0.5rem; | |
} | |
.item-url a { | |
color: #3b82f6; | |
text-decoration: none; | |
} | |
.item-url a:hover { | |
text-decoration: underline; | |
} | |
.item-description { | |
color: var(--text-secondary); | |
margin: 0; | |
} | |
.item-meta { | |
display: flex; | |
gap: 1rem; | |
font-size: var(--font-size-sm); | |
color: var(--text-muted); | |
} | |
.scraping-logs { | |
background: #f8fafc; | |
border-radius: var(--border-radius); | |
padding: 1rem; | |
} | |
.logs-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-block-end: 1rem; | |
} | |
.logs-container { | |
max-block-size: 300px; | |
overflow-y: auto; | |
background: white; | |
border-radius: var(--border-radius-sm); | |
padding: 0.8rem; | |
} | |
.log-entry { | |
display: flex; | |
gap: 0.8rem; | |
padding: 0.5rem; | |
border-radius: var(--border-radius-sm); | |
margin-block-end: 0.3rem; | |
font-size: var(--font-size-sm); | |
} | |
.log-entry.info { | |
background: #dbeafe; | |
color: #1e40af; | |
} | |
.log-entry.success { | |
background: #d1fae5; | |
color: #065f46; | |
} | |
.log-entry.error { | |
background: #fee2e2; | |
color: #991b1b; | |
} | |
.log-timestamp { | |
font-weight: 500; | |
min-inline-size: 80px; | |
} | |
.log-message { | |
flex: 1; | |
} | |
/* Button Styles */ | |
.btn { | |
display: inline-flex; | |
align-items: center; | |
gap: 0.5rem; | |
padding: 0.6rem 1.2rem; | |
border: none; | |
border-radius: var(--border-radius); | |
font-size: var(--font-size-base); | |
font-weight: 500; | |
cursor: pointer; | |
transition: var(--transition-smooth); | |
text-decoration: none; | |
} | |
.btn-primary { | |
background: var(--primary-gradient); | |
color: white; | |
} | |
.btn-primary:hover { | |
transform: translateY(-2px); | |
box-shadow: var(--shadow-glow-primary); | |
} | |
.btn-secondary { | |
background: #64748b; | |
color: white; | |
} | |
.btn-secondary:hover { | |
background: #475569; | |
transform: translateY(-2px); | |
} | |
.btn-danger { | |
background: var(--danger-gradient); | |
color: white; | |
} | |
.btn-danger:hover { | |
transform: translateY(-2px); | |
box-shadow: var(--shadow-glow-danger); | |
} | |
.btn-sm { | |
padding: 0.4rem 0.8rem; | |
font-size: var(--font-size-sm); | |
} | |
/* Utility Classes */ | |
.no-files, | |
.no-documents, | |
.no-results { | |
text-align: center; | |
color: var(--text-muted); | |
padding: 2rem; | |
font-style: italic; | |
} | |
.sr-only { | |
position: absolute; | |
inline-size: 1px; | |
block-size: 1px; | |
padding: 0; | |
margin: -1px; | |
overflow: hidden; | |
clip: rect(0, 0, 0, 0); | |
white-space: nowrap; | |
border: 0; | |
} | |
/* Enhanced Analytics Dashboard Styles */ | |
.analytics-dashboard { | |
margin-block-end: 2rem; | |
} | |
.analytics-overview { | |
margin-block-end: 1.5rem; | |
} | |
.analytics-grid { | |
display: grid; | |
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); | |
gap: 1.5rem; | |
margin-block-end: 1.5rem; | |
} | |
.analytics-card { | |
background: var(--card-bg); | |
backdrop-filter: blur(10px); | |
-webkit-backdrop-filter: blur(10px); | |
border-radius: var(--border-radius-lg); | |
padding: 1.5rem; | |
box-shadow: var(--shadow-md); | |
border: 1px solid var(--glass-border); | |
transition: var(--transition-smooth); | |
position: relative; | |
overflow: hidden; | |
} | |
.analytics-card::before { | |
content: ''; | |
position: absolute; | |
inset-block-start: 0; | |
inset-inline-start: 0; | |
inset-inline-end: 0; | |
block-size: 4px; | |
background: var(--primary-gradient); | |
} | |
.analytics-card:hover { | |
transform: translateY(-2px); | |
box-shadow: var(--shadow-lg); | |
} | |
.analytics-header { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
margin-block-end: 1rem; | |
} | |
.analytics-title { | |
font-size: var(--font-size-lg); | |
font-weight: 600; | |
color: var(--text-primary); | |
display: flex; | |
align-items: center; | |
gap: 0.5rem; | |
margin: 0; | |
} | |
.analytics-actions { | |
display: flex; | |
gap: 0.5rem; | |
} | |
.analytics-content { | |
display: flex; | |
flex-direction: column; | |
gap: 1rem; | |
} | |
/* Overview Stats */ | |
.overview-stats { | |
display: grid; | |
grid-template-columns: repeat(3, 1fr); | |
gap: 1rem; | |
margin-block-end: 1rem; | |
} | |
.overview-stat { | |
text-align: center; | |
padding: 1rem; | |
background: rgba(255, 255, 255, 0.5); | |
border-radius: var(--border-radius); | |
border: 1px solid rgba(59, 130, 246, 0.1); | |
} | |
.stat-number { | |
font-size: 1.5rem; | |
font-weight: 700; | |
color: var(--text-primary); | |
margin-block-end: 0.25rem; | |
} | |
.stat-label { | |
font-size: var(--font-size-sm); | |
color: var(--text-secondary); | |
} | |
/* Trends Insights */ | |
.trends-insights { | |
display: flex; | |
flex-direction: column; | |
gap: 0.5rem; | |
} | |
.insight-item { | |
display: flex; | |
align-items: center; | |
gap: 0.5rem; | |
padding: 0.5rem; | |
background: rgba(255, 255, 255, 0.5); | |
border-radius: var(--border-radius-sm); | |
font-size: var(--font-size-sm); | |
} | |
.text-success { color: #10b981; } | |
.text-warning { color: #f59e0b; } | |
.text-danger { color: #ef4444; } | |
/* Predictions Forecast */ | |
.predictions-forecast { | |
display: flex; | |
flex-direction: column; | |
gap: 0.5rem; | |
} | |
.forecast-item { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
padding: 0.5rem; | |
background: rgba(255, 255, 255, 0.5); | |
border-radius: var(--border-radius-sm); | |
} | |
.forecast-label { | |
font-size: var(--font-size-sm); | |
color: var(--text-secondary); | |
} | |
.forecast-value { | |
font-weight: 600; | |
color: var(--text-primary); | |
} | |
/* Quality Metrics */ | |
.quality-metrics { | |
display: grid; | |
grid-template-columns: repeat(3, 1fr); | |
gap: 1rem; | |
margin-block-end: 1rem; | |
} | |
.quality-metric { | |
text-align: center; | |
padding: 0.75rem; | |
background: rgba(255, 255, 255, 0.5); | |
border-radius: var(--border-radius-sm); | |
} | |
.metric-label { | |
font-size: var(--font-size-sm); | |
color: var(--text-secondary); | |
margin-block-end: 0.25rem; | |
} | |
.metric-value { | |
font-weight: 600; | |
color: var(--text-primary); | |
} | |
/* Health Status */ | |
.health-status { | |
margin-block-end: 1rem; | |
} | |
.status-indicator { | |
display: flex; | |
align-items: center; | |
gap: 0.5rem; | |
padding: 0.5rem; | |
background: rgba(255, 255, 255, 0.5); | |
border-radius: var(--border-radius-sm); | |
font-size: var(--font-size-sm); | |
} | |
.status-indicator i { | |
color: #10b981; | |
} | |
.health-metrics { | |
display: grid; | |
grid-template-columns: repeat(3, 1fr); | |
gap: 1rem; | |
margin-block-end: 1rem; | |
} | |
.health-metric { | |
text-align: center; | |
padding: 0.75rem; | |
background: rgba(255, 255, 255, 0.5); | |
border-radius: var(--border-radius-sm); | |
} | |
/* Clustering Summary */ | |
.clustering-summary { | |
display: flex; | |
justify-content: space-between; | |
margin-block-end: 1rem; | |
} | |
.cluster-count, .cluster-avg { | |
text-align: center; | |
padding: 0.75rem; | |
background: rgba(255, 255, 255, 0.5); | |
border-radius: var(--border-radius-sm); | |
flex: 1; | |
margin: 0 0.5rem; | |
} | |
.count-label, .avg-label { | |
font-size: var(--font-size-sm); | |
color: var(--text-secondary); | |
margin-block-end: 0.25rem; | |
} | |
.count-value, .avg-value { | |
font-weight: 600; | |
color: var(--text-primary); | |
} | |
/* Chart Containers */ | |
.overview-chart, .trends-chart, .predictions-chart, | |
.quality-chart, .health-chart, .clustering-chart, | |
.similarity-chart { | |
block-size: 200px; | |
position: relative; | |
} | |
/* Responsive Design */ | |
@media (max-inline-size: 768px) { | |
.analytics-grid { | |
grid-template-columns: 1fr; | |
} | |
.overview-stats, .quality-metrics, .health-metrics { | |
grid-template-columns: 1fr; | |
} | |
.clustering-summary { | |
flex-direction: column; | |
gap: 0.5rem; | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<div class="dashboard-container"> | |
<!-- سایدبار --> | |
<aside class="sidebar" id="sidebar"> | |
<div class="sidebar-header"> | |
<div class="logo"> | |
<div class="logo-icon"> | |
<i class="fas fa-scale-balanced"></i> | |
</div> | |
<div class="logo-text">سامانه حقوقی</div> | |
</div> | |
<button type="button" class="sidebar-toggle" onclick="toggleSidebar()" aria-label="تغییر وضعیت منو"> | |
<i class="fas fa-bars"></i> | |
</button> | |
</div> | |
<nav> | |
<div class="nav-section"> | |
<h6 class="nav-title">داشبورد</h6> | |
<ul class="nav-menu"> | |
<li class="nav-item"> | |
<a href="index.html" class="nav-link active" data-tooltip="نمای کلی"> | |
<i class="fas fa-chart-pie nav-icon"></i> | |
<span>نمای کلی</span> | |
</a> | |
</li> | |
</ul> | |
</div> | |
<div class="nav-section"> | |
<h6 class="nav-title">مدیریت اسناد</h6> | |
<ul class="nav-menu"> | |
<li class="nav-item"> | |
<a href="documents.html" class="nav-link" data-tooltip="مدیریت اسناد"> | |
<i class="fas fa-file-alt nav-icon"></i> | |
<span>مدیریت اسناد</span> | |
<span class="nav-badge" id="totalDocumentsBadge">6</span> | |
</a> | |
</li> | |
<li class="nav-item"> | |
<a href="upload.html" class="nav-link" data-tooltip="آپلود فایل"> | |
<i class="fas fa-cloud-upload-alt nav-icon"></i> | |
<span>آپلود فایل</span> | |
</a> | |
</li> | |
<li class="nav-item"> | |
<a href="search.html" class="nav-link" data-tooltip="جستجو"> | |
<i class="fas fa-search nav-icon"></i> | |
<span>جستجو</span> | |
</a> | |
</li> | |
</ul> | |
</div> | |
<div class="nav-section"> | |
<h6 class="nav-title">ابزارها</h6> | |
<ul class="nav-menu"> | |
<li class="nav-item"> | |
<a href="scraping.html" class="nav-link" data-tooltip="استخراج محتوا"> | |
<i class="fas fa-globe nav-icon"></i> | |
<span>استخراج محتوا</span> | |
</a> | |
</li> | |
<li class="nav-item"> | |
<a href="analytics.html" class="nav-link" data-tooltip="آمار و تحلیل"> | |
<i class="fas fa-chart-line nav-icon"></i> | |
<span>آمار و تحلیل</span> | |
</a> | |
</li> | |
<li class="nav-item"> | |
<a href="reports.html" class="nav-link" data-tooltip="گزارشها"> | |
<i class="fas fa-file-export nav-icon"></i> | |
<span>گزارشها</span> | |
</a> | |
</li> | |
</ul> | |
</div> | |
<div class="nav-section"> | |
<h6 class="nav-title">تنظیمات</h6> | |
<ul class="nav-menu"> | |
<li class="nav-item"> | |
<a href="settings.html" class="nav-link" data-tooltip="تنظیمات"> | |
<i class="fas fa-cog nav-icon"></i> | |
<span>تنظیمات</span> | |
</a> | |
</li> | |
<li class="nav-item"> | |
<a href="#" class="nav-link" data-tooltip="خروج"> | |
<i class="fas fa-sign-out-alt nav-icon"></i> | |
<span>خروج</span> | |
</a> | |
</li> | |
</ul> | |
</div> | |
</nav> | |
</aside> | |
<!-- محتوای اصلی --> | |
<main class="main-content" id="mainContent"> | |
<!-- هدر --> | |
<header class="dashboard-header"> | |
<div> | |
<h1 class="dashboard-title"> | |
<i class="fas fa-chart-pie title-icon"></i> | |
<span>داشبورد مدیریتی حقوقی</span> | |
</h1> | |
</div> | |
<div class="header-actions"> | |
<button type="button" class="mobile-menu-toggle" id="mobileMenuToggle" aria-label="منوی موبایل"> | |
<i class="fas fa-bars"></i> | |
</button> | |
<div class="search-container"> | |
<input type="text" class="search-input" id="searchInput" placeholder="جستجو در اسناد، قوانین، پروندهها..."> | |
<i class="fas fa-search search-icon"></i> | |
</div> | |
<div class="user-profile"> | |
<div class="user-avatar">ح</div> | |
<div class="user-info"> | |
<span class="user-name">حسین محمدی</span> | |
<span class="user-role">وکیل پایه یک</span> | |
</div> | |
<i class="fas fa-chevron-down"></i> | |
</div> | |
</div> | |
</header> | |
<!-- کارتهای آمار --> | |
<section aria-labelledby="stats-section"> | |
<h2 id="stats-section" class="sr-only">آمار و ارقام کلیدی</h2> | |
<div class="stats-grid"> | |
<div class="stat-card primary animate-fade-in"> | |
<div class="stat-header"> | |
<div class="stat-content"> | |
<div class="stat-title">کل اسناد جمعآوری شده</div> | |
<div class="stat-value" id="totalDocuments">6</div> | |
<div class="stat-extra">در پایگاه داده سیستم</div> | |
<div class="stat-change positive"> | |
<i class="fas fa-arrow-up"></i> | |
<span>+15.2%</span> | |
</div> | |
</div> | |
<div class="stat-icon primary"> | |
<i class="fas fa-file-alt"></i> | |
</div> | |
</div> | |
</div> | |
<div class="stat-card success animate-fade-in"> | |
<div class="stat-header"> | |
<div class="stat-content"> | |
<div class="stat-title">اسناد پردازش شده</div> | |
<div class="stat-value" id="processedDocuments">4</div> | |
<div class="stat-extra">با موفقیت پردازش شده</div> | |
<div class="stat-change positive"> | |
<i class="fas fa-arrow-up"></i> | |
<span>+23.1%</span> | |
</div> | |
</div> | |
<div class="stat-icon success"> | |
<i class="fas fa-check-circle"></i> | |
</div> | |
</div> | |
</div> | |
<div class="stat-card danger animate-fade-in"> | |
<div class="stat-header"> | |
<div class="stat-content"> | |
<div class="stat-title">اسناد دارای خطا</div> | |
<div class="stat-value" id="errorDocuments">1</div> | |
<div class="stat-extra">نیازمند بررسی</div> | |
<div class="stat-change negative"> | |
<i class="fas fa-arrow-down"></i> | |
<span>-8.3%</span> | |
</div> | |
</div> | |
<div class="stat-icon danger"> | |
<i class="fas fa-triangle-exclamation"></i> | |
</div> | |
</div> | |
</div> | |
<div class="stat-card warning animate-fade-in"> | |
<div class="stat-header"> | |
<div class="stat-content"> | |
<div class="stat-title">امتیاز کیفی میانگین</div> | |
<div class="stat-value" id="averageQuality">8.1</div> | |
<div class="stat-extra">از 10 امتیاز</div> | |
<div class="stat-change positive"> | |
<i class="fas fa-arrow-up"></i> | |
<span>+2.1%</span> | |
</div> | |
</div> | |
<div class="stat-icon warning"> | |
<i class="fas fa-star"></i> | |
</div> | |
</div> | |
</div> | |
</div> | |
</section> | |
<!-- نمودارها --> | |
<section class="charts-section"> | |
<div class="chart-card animate-fade-in"> | |
<div class="chart-header"> | |
<h2 class="chart-title">روند پردازش اسناد</h2> | |
<div class="chart-filters"> | |
<button type="button" class="chart-filter" onclick="updateChart('daily')">روزانه</button> | |
<button type="button" class="chart-filter active" onclick="updateChart('weekly')">هفتگی</button> | |
<button type="button" class="chart-filter" onclick="updateChart('monthly')">ماهانه</button> | |
</div> | |
</div> | |
<div class="chart-container" id="documentsChart"> | |
<div class="chart-placeholder" id="chartPlaceholder"> | |
<i class="fas fa-chart-line"></i> | |
<p>نمودار روند پردازش</p> | |
<small>Chart.js در حال بارگذاری...</small> | |
</div> | |
<canvas id="documentsChartCanvas" style="display: none;"></canvas> | |
</div> | |
</div> | |
<div class="chart-card animate-fade-in"> | |
<div class="chart-header"> | |
<h2 class="chart-title">توزیع وضعیت اسناد</h2> | |
<div class="chart-filters"> | |
<button type="button" class="chart-filter active" onclick="updateStatusChart('status')">وضعیت</button> | |
<button type="button" class="chart-filter" onclick="updateStatusChart('category')">دستهبندی</button> | |
</div> | |
</div> | |
<div class="chart-container" id="statusChart"> | |
<div class="chart-placeholder" id="statusPlaceholder"> | |
<i class="fas fa-chart-pie"></i> | |
<p>نمودار توزیع وضعیت</p> | |
<small>Chart.js در حال بارگذاری...</small> | |
</div> | |
<canvas id="statusChartCanvas" style="display: none;"></canvas> | |
</div> | |
</div> | |
</section> | |
<!-- Enhanced Analytics Dashboard --> | |
<section class="analytics-dashboard animate-fade-in"> | |
<div class="section-header"> | |
<h2 class="section-title"> | |
<i class="fas fa-chart-line"></i> | |
داشبورد تحلیلی پیشرفته | |
</h2> | |
<div class="section-actions"> | |
<button type="button" class="btn btn-primary" id="refreshAnalyticsBtn"> | |
<i class="fas fa-sync-alt"></i> | |
بروزرسانی تحلیلها | |
</button> | |
</div> | |
</div> | |
<!-- Analytics Overview --> | |
<div class="analytics-overview"> | |
<div class="analytics-card"> | |
<div class="analytics-header"> | |
<h3 class="analytics-title"> | |
<i class="fas fa-eye"></i> | |
نمای کلی | |
</h3> | |
<div class="analytics-actions"> | |
<button type="button" class="btn btn-sm btn-secondary" onclick="refreshOverview()" aria-label="بروزرسانی نمای کلی"> | |
<i class="fas fa-refresh"></i> | |
</button> | |
</div> | |
</div> | |
<div class="analytics-content" id="overviewContent"> | |
<div class="overview-stats"> | |
<div class="overview-stat"> | |
<div class="stat-number" id="totalAnalytics">0</div> | |
<div class="stat-label">کل تحلیلها</div> | |
</div> | |
<div class="overview-stat"> | |
<div class="stat-number" id="activeAnalytics">0</div> | |
<div class="stat-label">تحلیلهای فعال</div> | |
</div> | |
<div class="overview-stat"> | |
<div class="stat-number" id="accuracyRate">0%</div> | |
<div class="stat-label">دقت تحلیل</div> | |
</div> | |
</div> | |
<div class="overview-chart" id="overviewChart"> | |
<canvas id="overviewChartCanvas"></canvas> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Analytics Grid --> | |
<div class="analytics-grid"> | |
<!-- Trends Analytics --> | |
<div class="analytics-card"> | |
<div class="analytics-header"> | |
<h3 class="analytics-title"> | |
<i class="fas fa-trending-up"></i> | |
روندها و الگوها | |
</h3> | |
<div class="analytics-actions"> | |
<button type="button" class="btn btn-sm btn-secondary" onclick="refreshTrends()" aria-label="بروزرسانی روندها"> | |
<i class="fas fa-refresh"></i> | |
</button> | |
</div> | |
</div> | |
<div class="analytics-content" id="trendsContent"> | |
<div class="trends-chart" id="trendsChart"> | |
<canvas id="trendsChartCanvas"></canvas> | |
</div> | |
<div class="trends-insights" id="trendsInsights"> | |
<div class="insight-item"> | |
<i class="fas fa-arrow-up text-success"></i> | |
<span>افزایش 15% در پردازش اسناد</span> | |
</div> | |
<div class="insight-item"> | |
<i class="fas fa-clock text-warning"></i> | |
<span>کاهش زمان پردازش به 2.3 ثانیه</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Predictions Analytics --> | |
<div class="analytics-card"> | |
<div class="analytics-header"> | |
<h3 class="analytics-title"> | |
<i class="fas fa-crystal-ball"></i> | |
پیشبینیها | |
</h3> | |
<div class="analytics-actions"> | |
<button type="button" class="btn btn-sm btn-secondary" onclick="refreshPredictions()" aria-label="بروزرسانی پیشبینیها"> | |
<i class="fas fa-refresh"></i> | |
</button> | |
</div> | |
</div> | |
<div class="analytics-content" id="predictionsContent"> | |
<div class="predictions-chart" id="predictionsChart"> | |
<canvas id="predictionsChartCanvas"></canvas> | |
</div> | |
<div class="predictions-forecast" id="predictionsForecast"> | |
<div class="forecast-item"> | |
<div class="forecast-label">پیشبینی هفته آینده:</div> | |
<div class="forecast-value" id="weeklyForecast">0</div> | |
</div> | |
<div class="forecast-item"> | |
<div class="forecast-label">اعتماد به پیشبینی:</div> | |
<div class="forecast-value" id="confidenceLevel">0%</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Quality Analytics --> | |
<div class="analytics-card"> | |
<div class="analytics-header"> | |
<h3 class="analytics-title"> | |
<i class="fas fa-award"></i> | |
ارزیابی کیفیت | |
</h3> | |
<div class="analytics-actions"> | |
<button type="button" class="btn btn-sm btn-secondary" onclick="refreshQuality()" aria-label="بروزرسانی کیفیت"> | |
<i class="fas fa-refresh"></i> | |
</button> | |
</div> | |
</div> | |
<div class="analytics-content" id="qualityContent"> | |
<div class="quality-metrics"> | |
<div class="quality-metric"> | |
<div class="metric-label">امتیاز کلی:</div> | |
<div class="metric-value" id="overallQuality">0</div> | |
</div> | |
<div class="quality-metric"> | |
<div class="metric-label">خوانایی:</div> | |
<div class="metric-value" id="readabilityScore">0</div> | |
</div> | |
<div class="quality-metric"> | |
<div class="metric-label">دقت:</div> | |
<div class="metric-value" id="accuracyScore">0</div> | |
</div> | |
</div> | |
<div class="quality-chart" id="qualityChart"> | |
<canvas id="qualityChartCanvas"></canvas> | |
</div> | |
</div> | |
</div> | |
<!-- Health Analytics --> | |
<div class="analytics-card"> | |
<div class="analytics-header"> | |
<h3 class="analytics-title"> | |
<i class="fas fa-heartbeat"></i> | |
سلامت سیستم | |
</h3> | |
<div class="analytics-actions"> | |
<button type="button" class="btn btn-sm btn-secondary" onclick="refreshHealth()" aria-label="Refresh system health"> | |
<i class="fas fa-refresh"></i> | |
</button> | |
</div> | |
</div> | |
<div class="analytics-content" id="healthContent"> | |
<div class="health-status"> | |
<div class="status-indicator" id="systemStatus"> | |
<i class="fas fa-circle"></i> | |
<span>وضعیت سیستم</span> | |
</div> | |
</div> | |
<div class="health-metrics"> | |
<div class="health-metric"> | |
<div class="metric-label">CPU:</div> | |
<div class="metric-value" id="cpuUsage">0%</div> | |
</div> | |
<div class="health-metric"> | |
<div class="metric-label">RAM:</div> | |
<div class="metric-value" id="memoryUsage">0%</div> | |
</div> | |
<div class="health-metric"> | |
<div class="metric-label">دیسک:</div> | |
<div class="metric-value" id="diskUsage">0%</div> | |
</div> | |
</div> | |
<div class="health-chart" id="healthChart"> | |
<canvas id="healthChartCanvas"></canvas> | |
</div> | |
</div> | |
</div> | |
<!-- Clustering Analytics --> | |
<div class="analytics-card"> | |
<div class="analytics-header"> | |
<h3 class="analytics-title"> | |
<i class="fas fa-sitemap"></i> | |
خوشهبندی اسناد | |
</h3> | |
<div class="analytics-actions"> | |
<button type="button" class="btn btn-sm btn-secondary" onclick="refreshClustering()" aria-label="Refresh clustering analysis"> | |
<i class="fas fa-refresh"></i> | |
</button> | |
</div> | |
</div> | |
<div class="analytics-content" id="clusteringContent"> | |
<div class="clustering-summary"> | |
<div class="cluster-count"> | |
<div class="count-label">تعداد خوشهها:</div> | |
<div class="count-value" id="clusterCount">0</div> | |
</div> | |
<div class="cluster-avg"> | |
<div class="avg-label">میانگین شباهت:</div> | |
<div class="avg-value" id="avgSimilarity">0%</div> | |
</div> | |
</div> | |
<div class="clustering-chart" id="clusteringChart"> | |
<canvas id="clusteringChartCanvas"></canvas> | |
</div> | |
<div class="clustering-list" id="clusteringList"> | |
<!-- Cluster items will be loaded here --> | |
</div> | |
</div> | |
</div> | |
<!-- Similarity Analytics --> | |
<div class="analytics-card"> | |
<div class="analytics-header"> | |
<h3 class="analytics-title"> | |
<i class="fas fa-search-plus"></i> | |
تحلیل شباهت | |
</h3> | |
<div class="analytics-actions"> | |
<button type="button" class="btn btn-sm btn-secondary" onclick="refreshSimilarity()" aria-label="Refresh similarity analysis"> | |
<i class="fas fa-refresh"></i> | |
</button> | |
</div> | |
</div> | |
<div class="analytics-content" id="similarityContent"> | |
<div class="similarity-results" id="similarityResults"> | |
<!-- Similarity results will be loaded here --> | |
</div> | |
<div class="similarity-chart" id="similarityChart"> | |
<canvas id="similarityChartCanvas"></canvas> | |
</div> | |
</div> | |
</div> | |
</div> | |
</section> | |
<!-- File Upload Section --> | |
<section class="upload-section animate-fade-in"> | |
<div class="section-header"> | |
<h2 class="section-title"> | |
<i class="fas fa-upload"></i> | |
آپلود و پردازش اسناد | |
</h2> | |
</div> | |
<div class="upload-container"> | |
<div class="upload-drop-zone" id="uploadDropZone"> | |
<div class="upload-content"> | |
<i class="fas fa-cloud-upload-alt upload-icon"></i> | |
<h3>فایلهای خود را اینجا رها کنید</h3> | |
<p>یا کلیک کنید تا فایل انتخاب کنید</p> | |
<input type="file" id="documentUpload" multiple accept=".pdf,.jpg,.jpeg,.png,.tiff" style="display: none;"> | |
<button type="button" class="upload-btn" onclick="document.getElementById('documentUpload').click()"> | |
انتخاب فایل | |
</button> | |
</div> | |
</div> | |
<div class="upload-queue" id="uploadQueue"> | |
<h4>فایلهای انتخاب شده</h4> | |
<div class="queue-content"> | |
<p class="no-files">هیچ فایلی برای آپلود انتخاب نشده</p> | |
</div> | |
</div> | |
<div class="upload-progress" id="uploadProgress" style="display: none;"> | |
<div class="progress-bar"> | |
<div class="progress-fill" id="uploadProgressBar"></div> | |
</div> | |
<p class="progress-text" id="uploadProgressText">آپلود در حال انجام...</p> | |
</div> | |
<div class="upload-actions"> | |
<button type="button" class="btn btn-primary" id="uploadButton"> | |
<i class="fas fa-upload"></i> | |
شروع آپلود | |
</button> | |
</div> | |
</div> | |
<div class="ocr-results" id="ocrResults"> | |
<h4>نتایج پردازش OCR</h4> | |
<div class="results-content"> | |
<!-- OCR results will be displayed here --> | |
</div> | |
</div> | |
</section> | |
<!-- Document Management Section --> | |
<section class="documents-section animate-fade-in"> | |
<div class="section-header"> | |
<h2 class="section-title"> | |
<i class="fas fa-folder-open"></i> | |
مدیریت اسناد | |
</h2> | |
<div class="section-actions"> | |
<button type="button" class="btn btn-primary" id="createDocumentBtn"> | |
<i class="fas fa-plus"></i> | |
ایجاد سند جدید | |
</button> | |
</div> | |
</div> | |
<div class="documents-filters"> | |
<div class="filter-group"> | |
<input type="text" id="documentSearch" placeholder="جستجو در اسناد..." class="search-input"> | |
</div> | |
<div class="filter-group"> | |
<select id="statusFilter" class="filter-select"> | |
<option value="all">همه وضعیتها</option> | |
<option value="pending">در انتظار</option> | |
<option value="processing">در حال پردازش</option> | |
<option value="completed">تکمیل شده</option> | |
<option value="error">خطا</option> | |
</select> | |
</div> | |
<div class="filter-group"> | |
<select id="categoryFilter" class="filter-select"> | |
<option value="all">همه دستهها</option> | |
<option value="contracts">قراردادها</option> | |
<option value="lawsuits">دادخواستها</option> | |
<option value="judgments">احکام قضایی</option> | |
<option value="appeals">آرای دیوان</option> | |
<option value="other">سایر</option> | |
</select> | |
</div> | |
<div class="filter-group"> | |
<input type="date" id="dateFromFilter" class="date-input"> | |
</div> | |
<div class="filter-group"> | |
<input type="date" id="dateToFilter" class="date-input"> | |
</div> | |
</div> | |
<div class="documents-list" id="documentsList"> | |
<!-- Documents will be loaded here --> | |
</div> | |
</section> | |
<!-- Scraping Control Section --> | |
<section class="scraping-section animate-fade-in"> | |
<div class="section-header"> | |
<h2 class="section-title"> | |
<i class="fas fa-spider"></i> | |
کنترل اسکرپینگ وب | |
</h2> | |
</div> | |
<div class="scraping-controls"> | |
<div class="control-group"> | |
<label for="scrapingUrl">آدرس وبسایت:</label> | |
<input type="url" id="scrapingUrl" placeholder="https://example.com" class="form-input"> | |
</div> | |
<div class="control-group"> | |
<label for="scrapingDepth">عمق اسکرپینگ:</label> | |
<select id="scrapingDepth" class="form-select"> | |
<option value="1">سطح 1</option> | |
<option value="2">سطح 2</option> | |
<option value="3">سطح 3</option> | |
</select> | |
</div> | |
<div class="control-group"> | |
<label for="maxPages">حداکثر صفحات:</label> | |
<input type="number" id="maxPages" value="100" min="1" max="1000" class="form-input"> | |
</div> | |
<div class="control-group"> | |
<label for="scrapingFilters">فیلترها:</label> | |
<input type="text" id="scrapingFilters" placeholder="کلمات کلیدی (اختیاری)" class="form-input"> | |
</div> | |
</div> | |
<div class="scraping-actions"> | |
<button type="button" class="btn btn-primary" id="startScrapingBtn"> | |
<i class="fas fa-play"></i> | |
شروع اسکرپینگ | |
</button> | |
<button type="button" class="btn btn-danger" id="stopScrapingBtn"> | |
<i class="fas fa-stop"></i> | |
توقف اسکرپینگ | |
</button> | |
<button type="button" class="btn btn-secondary" id="refreshResultsBtn"> | |
<i class="fas fa-refresh"></i> | |
بروزرسانی نتایج | |
</button> | |
</div> | |
<div class="scraping-status"> | |
<div class="status-info"> | |
<span class="status-label">وضعیت:</span> | |
<span class="status-value" id="scrapingStatus">آماده</span> | |
</div> | |
<div class="progress-container"> | |
<div class="progress-bar"> | |
<div class="progress-fill" id="scrapingProgress">0%</div> | |
</div> | |
</div> | |
<div class="scraping-stats" id="scrapingStats"> | |
<!-- Statistics will be displayed here --> | |
</div> | |
</div> | |
<div class="scraping-results"> | |
<h4>نتایج اسکرپینگ</h4> | |
<div class="results-container" id="scrapingResults"> | |
<!-- Results will be displayed here --> | |
</div> | |
</div> | |
<div class="scraping-logs"> | |
<div class="logs-header"> | |
<h4>لاگهای اسکرپینگ</h4> | |
<button type="button" class="btn btn-sm btn-secondary" id="clearLogsBtn"> | |
<i class="fas fa-trash"></i> | |
پاک کردن لاگها | |
</button> | |
</div> | |
<div class="logs-container" id="scrapingLogs"> | |
<!-- Logs will be displayed here --> | |
</div> | |
</div> | |
</section> | |
</main> | |
</div> | |
<!-- Toast Container --> | |
<div class="toast-container" id="toastContainer"></div> | |
<!-- Connection Status --> | |
<div class="connection-status online" id="connectionStatus"> | |
<div class="status-indicator"></div> | |
<span>متصل به سرور</span> | |
</div> | |
<script> | |
// API Configuration | |
const API_ENDPOINTS = { | |
// Dashboard endpoints | |
dashboardSummary: '/api/dashboard/summary', | |
chartsData: '/api/dashboard/charts-data', | |
aiSuggestions: '/api/dashboard/ai-suggestions', | |
trainAI: '/api/dashboard/ai-feedback', | |
performanceMetrics: '/api/dashboard/performance-metrics', | |
trends: '/api/dashboard/trends', | |
// Documents endpoints | |
documents: '/api/documents', | |
documentSearch: '/api/documents/search/', | |
categories: '/api/documents/categories/', | |
sources: '/api/documents/sources/', | |
// OCR endpoints | |
ocrProcess: '/api/ocr/process', | |
ocrProcessAndSave: '/api/ocr/process-and-save', | |
ocrBatchProcess: '/api/ocr/batch-process', | |
ocrQualityMetrics: '/api/ocr/quality-metrics', | |
ocrModels: '/api/ocr/models', | |
ocrStatus: '/api/ocr/status', | |
// Analytics endpoints | |
analyticsOverview: '/api/analytics/overview', | |
analyticsTrends: '/api/analytics/trends', | |
analyticsSimilarity: '/api/analytics/similarity', | |
analyticsPerformance: '/api/analytics/performance', | |
analyticsEntities: '/api/analytics/entities', | |
analyticsQuality: '/api/analytics/quality-analysis', | |
// Scraping endpoints | |
scrapingStart: '/api/scraping/scrape', | |
scrapingStatus: '/api/scraping/status', | |
scrapingItems: '/api/scraping/items', | |
scrapingStatistics: '/api/scraping/statistics', | |
ratingSummary: '/api/scraping/rating/summary', | |
// System endpoints | |
health: '/api/health' | |
}; | |
// Enhanced error handling | |
async function fetchWithErrorHandling(url, options = {}) { | |
try { | |
const response = await fetch(url, { | |
headers: { | |
'Content-Type': 'application/json', | |
...options.headers | |
}, | |
...options | |
}); | |
if (!response.ok) { | |
throw new Error(`HTTP ${response.status}: ${response.statusText}`); | |
} | |
return await response.json(); | |
} catch (error) { | |
console.error(`API Error (${url}):`, error); | |
showToast(`خطا در اتصال به سرور: ${error.message}`, 'error'); | |
throw error; | |
} | |
} | |
// Global variables | |
let documentsChart = null; | |
let statusChart = null; | |
let chartJsLoaded = false; | |
// Initialize when page loads | |
document.addEventListener('DOMContentLoaded', function() { | |
console.log('Dashboard loading...'); | |
// Check if Chart.js is loaded | |
setTimeout(() => { | |
chartJsLoaded = typeof Chart !== 'undefined'; | |
console.log('Chart.js loaded:', chartJsLoaded); | |
if (chartJsLoaded) { | |
initializeCharts(); | |
} else { | |
console.warn('Chart.js not loaded, keeping placeholders'); | |
showToast('Chart.js بارگذاری نشد - نمودارها غیرفعال هستند', 'warning', 'هشدار'); | |
} | |
}, 1000); | |
setupEventListeners(); | |
loadInitialData(); | |
showToast('داشبورد با موفقیت بارگذاری شد', 'success', 'خوش آمدید'); | |
}); | |
// Initialize charts if Chart.js is available | |
function initializeCharts() { | |
if (!chartJsLoaded) return; | |
try { | |
// Hide placeholders and show canvases | |
document.getElementById('chartPlaceholder').style.display = 'none'; | |
document.getElementById('statusPlaceholder').style.display = 'none'; | |
document.getElementById('documentsChartCanvas').style.display = 'block'; | |
document.getElementById('statusChartCanvas').style.display = 'block'; | |
// Processing trends chart | |
const documentsCtx = document.getElementById('documentsChartCanvas'); | |
if (documentsCtx) { | |
documentsChart = new Chart(documentsCtx, { | |
type: 'line', | |
data: { | |
labels: ['هفته 1', 'هفته 2', 'هفته 3', 'هفته 4'], | |
datasets: [ | |
{ | |
label: 'پردازش شده', | |
data: [85, 92, 78, 95], | |
borderColor: '#10b981', | |
backgroundColor: 'rgba(16, 185, 129, 0.1)', | |
tension: 0.4, | |
borderWidth: 3, | |
pointRadius: 6, | |
pointHoverRadius: 8 | |
}, | |
{ | |
label: 'آپلود شده', | |
data: [95, 105, 88, 110], | |
borderColor: '#3b82f6', | |
backgroundColor: 'rgba(59, 130, 246, 0.1)', | |
tension: 0.4, | |
borderWidth: 3, | |
pointRadius: 6, | |
pointHoverRadius: 8 | |
} | |
] | |
}, | |
options: { | |
responsive: true, | |
maintainAspectRatio: false, | |
plugins: { | |
legend: { | |
position: 'top', | |
labels: { | |
usePointStyle: true, | |
padding: 20, | |
font: { | |
family: 'Vazirmatn', | |
size: 12 | |
} | |
} | |
} | |
}, | |
scales: { | |
y: { | |
beginAtZero: true, | |
grid: { | |
color: 'rgba(0, 0, 0, 0.05)' | |
}, | |
ticks: { | |
font: { | |
family: 'Vazirmatn' | |
} | |
} | |
}, | |
x: { | |
grid: { | |
color: 'rgba(0, 0, 0, 0.05)' | |
}, | |
ticks: { | |
font: { | |
family: 'Vazirmatn' | |
} | |
} | |
} | |
}, | |
interaction: { | |
intersect: false, | |
mode: 'index' | |
} | |
} | |
}); | |
} | |
// Status distribution chart | |
const statusCtx = document.getElementById('statusChartCanvas'); | |
if (statusCtx) { | |
statusChart = new Chart(statusCtx, { | |
type: 'doughnut', | |
data: { | |
labels: ['پردازش شده', 'در حال پردازش', 'خطا', 'آپلود شده'], | |
datasets: [{ | |
data: [4, 1, 1, 0], | |
backgroundColor: ['#10b981', '#f59e0b', '#ef4444', '#3b82f6'], | |
borderColor: '#ffffff', | |
borderWidth: 3, | |
hoverBorderWidth: 5 | |
}] | |
}, | |
options: { | |
responsive: true, | |
maintainAspectRatio: false, | |
plugins: { | |
legend: { | |
position: 'bottom', | |
labels: { | |
usePointStyle: true, | |
padding: 15, | |
font: { | |
family: 'Vazirmatn', | |
size: 11 | |
} | |
} | |
} | |
}, | |
cutout: '60%' | |
} | |
}); | |
} | |
console.log('Charts initialized successfully'); | |
showToast('نمودارها بارگذاری شدند', 'success', 'موفقیت'); | |
} catch (error) { | |
console.error('Chart initialization failed:', error); | |
showToast('خطا در بارگذاری نمودارها', 'error', 'خطا'); | |
} | |
} | |
// Setup event listeners | |
function setupEventListeners() { | |
// Mobile menu toggle | |
const mobileMenuToggle = document.getElementById('mobileMenuToggle'); | |
const sidebar = document.getElementById('sidebar'); | |
if (mobileMenuToggle && sidebar) { | |
mobileMenuToggle.addEventListener('click', () => { | |
sidebar.classList.toggle('open'); | |
}); | |
// Close sidebar when clicking outside on mobile | |
document.addEventListener('click', (e) => { | |
if (window.innerWidth <= 992 && | |
sidebar.classList.contains('open') && | |
!sidebar.contains(e.target) && | |
!mobileMenuToggle.contains(e.target)) { | |
sidebar.classList.remove('open'); | |
} | |
}); | |
} | |
// Search functionality | |
const searchInput = document.getElementById('searchInput'); | |
if (searchInput) { | |
searchInput.addEventListener('input', function(e) { | |
const searchTerm = e.target.value.trim(); | |
if (searchTerm.length > 2) { | |
showToast(`جستجو برای: ${searchTerm}`, 'info', 'جستجو'); | |
} | |
}); | |
} | |
} | |
// Load initial data | |
async function loadInitialData() { | |
try { | |
await Promise.all([ | |
loadDashboardStats(), | |
loadChartsData(), | |
loadAISuggestions() | |
]); | |
showToast('دادهها با موفقیت بارگذاری شدند', 'success'); | |
} catch (error) { | |
console.error('Error loading initial data:', error); | |
showToast('خطا در بارگذاری دادهها', 'error'); | |
} | |
} | |
// Load dashboard statistics from API | |
async function loadDashboardStats() { | |
try { | |
const stats = await fetchWithErrorHandling(API_ENDPOINTS.dashboardSummary); | |
// Update UI with real data | |
document.getElementById('totalDocuments').textContent = stats.total_documents || 0; | |
document.getElementById('processedDocuments').textContent = stats.processed_documents || 0; | |
document.getElementById('errorDocuments').textContent = stats.error_documents || 0; | |
document.getElementById('averageQuality').textContent = (stats.average_quality || 0).toFixed(1); | |
// Update badge | |
const badge = document.getElementById('totalDocumentsBadge'); | |
if (badge) { | |
badge.textContent = stats.total_documents || 0; | |
} | |
console.log('Dashboard stats loaded from API'); | |
} catch (error) { | |
console.error('Failed to load dashboard stats:', error); | |
// Fallback to mock data | |
updateStatsWithMockData(); | |
} | |
} | |
// Load charts data from API | |
async function loadChartsData() { | |
try { | |
const chartsData = await fetchWithErrorHandling(API_ENDPOINTS.chartsData); | |
updateChartsWithRealData(chartsData); | |
console.log('Charts data loaded from API'); | |
} catch (error) { | |
console.error('Failed to load charts data:', error); | |
// Keep existing mock charts | |
} | |
} | |
// Load AI suggestions from API | |
async function loadAISuggestions() { | |
try { | |
const suggestions = await fetchWithErrorHandling(API_ENDPOINTS.aiSuggestions); | |
updateAISuggestions(suggestions.suggestions || []); | |
console.log('AI suggestions loaded from API'); | |
} catch (error) { | |
console.error('Failed to load AI suggestions:', error); | |
updateAISuggestions([]); | |
} | |
} | |
// Update charts with real data | |
function updateChartsWithRealData(chartsData) { | |
if (!chartJsLoaded || !documentsChart) return; | |
// Update charts with real data from API | |
if (chartsData.category_distribution) { | |
// Update status chart with real category data | |
const categories = Object.keys(chartsData.category_distribution); | |
const values = Object.values(chartsData.category_distribution); | |
if (statusChart) { | |
statusChart.data.labels = categories; | |
statusChart.data.datasets[0].data = values; | |
statusChart.update('active'); | |
} | |
} | |
} | |
// Update AI suggestions | |
function updateAISuggestions(suggestions) { | |
const suggestionsContainer = document.getElementById('aiSuggestionsList'); | |
if (!suggestionsContainer) return; | |
if (suggestions.length === 0) { | |
suggestionsContainer.innerHTML = '<p class="no-data">هیچ پیشنهادی یافت نشد</p>'; | |
return; | |
} | |
const suggestionsHTML = suggestions.map(suggestion => ` | |
<div class="suggestion-item"> | |
<h4>${suggestion.title || 'بدون عنوان'}</h4> | |
<p>${suggestion.description || 'توضیحات موجود نیست'}</p> | |
<span class="suggestion-score">امتیاز: ${suggestion.score || 0}</span> | |
</div> | |
`).join(''); | |
suggestionsContainer.innerHTML = suggestionsHTML; | |
} | |
// Fallback to mock data | |
function updateStatsWithMockData() { | |
const stats = { | |
total: 6, | |
processed: 4, | |
error: 1, | |
quality: 8.1 | |
}; | |
document.getElementById('totalDocuments').textContent = stats.total; | |
document.getElementById('processedDocuments').textContent = stats.processed; | |
document.getElementById('errorDocuments').textContent = stats.error; | |
document.getElementById('averageQuality').textContent = stats.quality.toFixed(1); | |
const badge = document.getElementById('totalDocumentsBadge'); | |
if (badge) { | |
badge.textContent = stats.total; | |
} | |
} | |
// Chart update functions | |
function updateChart(period) { | |
if (!chartJsLoaded || !documentsChart) { | |
showToast('نمودارها در دسترس نیستند', 'warning', 'هشدار'); | |
return; | |
} | |
// Update active filter | |
document.querySelectorAll('.chart-filter').forEach(btn => { | |
btn.classList.remove('active'); | |
}); | |
event.target.classList.add('active'); | |
// Mock data for different periods | |
const data = { | |
daily: { | |
labels: ['شنبه', 'یکشنبه', 'دوشنبه', 'سهشنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه'], | |
processed: [12, 19, 8, 15, 22, 18, 14], | |
uploaded: [15, 23, 12, 18, 25, 21, 16] | |
}, | |
weekly: { | |
labels: ['هفته 1', 'هفته 2', 'هفته 3', 'هفته 4'], | |
processed: [85, 92, 78, 95], | |
uploaded: [95, 105, 88, 110] | |
}, | |
monthly: { | |
labels: ['فروردین', 'اردیبهشت', 'خرداد', 'تیر', 'مرداد', 'شهریور'], | |
processed: [340, 380, 290, 420, 380, 450], | |
uploaded: [380, 420, 320, 460, 410, 490] | |
} | |
}; | |
const selectedData = data[period] || data.weekly; | |
documentsChart.data.labels = selectedData.labels; | |
documentsChart.data.datasets[0].data = selectedData.processed; | |
documentsChart.data.datasets[1].data = selectedData.uploaded; | |
documentsChart.update('active'); | |
showToast(`نمودار به حالت ${period === 'daily' ? 'روزانه' : period === 'weekly' ? 'هفتگی' : 'ماهانه'} تغییر کرد`, 'info', 'بروزرسانی'); | |
} | |
function updateStatusChart(type) { | |
if (!chartJsLoaded || !statusChart) { | |
showToast('نمودارها در دسترس نیستند', 'warning', 'هشدار'); | |
return; | |
} | |
// Update active filter | |
const chartCard = event.target.closest('.chart-card'); | |
chartCard.querySelectorAll('.chart-filter').forEach(btn => { | |
btn.classList.remove('active'); | |
}); | |
event.target.classList.add('active'); | |
const data = { | |
status: { | |
labels: ['پردازش شده', 'در حال پردازش', 'خطا', 'آپلود شده'], | |
data: [4, 1, 1, 0], | |
colors: ['#10b981', '#f59e0b', '#ef4444', '#3b82f6'] | |
}, | |
category: { | |
labels: ['قراردادها', 'دادخواستها', 'احکام قضایی', 'آرای دیوان', 'سایر'], | |
data: [1, 1, 1, 1, 2], | |
colors: ['#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6'] | |
} | |
}; | |
const selectedData = data[type] || data.status; | |
statusChart.data.labels = selectedData.labels; | |
statusChart.data.datasets[0].data = selectedData.data; | |
statusChart.data.datasets[0].backgroundColor = selectedData.colors; | |
statusChart.update('active'); | |
showToast(`نمودار به حالت ${type === 'status' ? 'وضعیت' : 'دستهبندی'} تغییر کرد`, 'info', 'بروزرسانی'); | |
} | |
// Utility functions | |
function toggleSidebar() { | |
const sidebar = document.getElementById('sidebar'); | |
const mainContent = document.getElementById('mainContent'); | |
if (sidebar && mainContent) { | |
sidebar.classList.toggle('collapsed'); | |
mainContent.classList.toggle('sidebar-collapsed'); | |
} | |
} | |
function showToast(message, type = 'info', title = 'اعلان') { | |
const toastContainer = document.getElementById('toastContainer'); | |
if (!toastContainer) return; | |
const toast = document.createElement('div'); | |
toast.className = `toast ${type}`; | |
const icons = { | |
success: 'check-circle', | |
error: 'exclamation-triangle', | |
warning: 'exclamation-circle', | |
info: 'info-circle' | |
}; | |
toast.innerHTML = ` | |
<div class="toast-icon"> | |
<i class="fas fa-${icons[type]}"></i> | |
</div> | |
<div class="toast-content"> | |
<div class="toast-title">${title}</div> | |
<div class="toast-message">${message}</div> | |
</div> | |
<button type="button" class="toast-close" onclick="this.parentElement.remove()"> | |
<i class="fas fa-times"></i> | |
</button> | |
`; | |
toastContainer.appendChild(toast); | |
// Show toast | |
setTimeout(() => toast.classList.add('show'), 100); | |
// Auto remove after 5 seconds | |
setTimeout(() => { | |
if (toast.parentElement) { | |
toast.classList.remove('show'); | |
setTimeout(() => { | |
if (toast.parentElement) { | |
toast.remove(); | |
} | |
}, 300); | |
} | |
}, 5000); | |
} | |
// Connection status monitoring | |
async function checkConnectionStatus() { | |
try { | |
const response = await fetch(API_ENDPOINTS.health); | |
const status = await response.json(); | |
const connectionStatus = document.getElementById('connectionStatus'); | |
if (connectionStatus) { | |
if (status.status === 'healthy') { | |
connectionStatus.className = 'connection-status online'; | |
connectionStatus.innerHTML = ` | |
<div class="status-indicator"></div> | |
<span>متصل به سرور</span> | |
`; | |
} else { | |
connectionStatus.className = 'connection-status offline'; | |
connectionStatus.innerHTML = ` | |
<div class="status-indicator"></div> | |
<span>خطا در اتصال</span> | |
`; | |
} | |
} | |
} catch (error) { | |
const connectionStatus = document.getElementById('connectionStatus'); | |
if (connectionStatus) { | |
connectionStatus.className = 'connection-status offline'; | |
connectionStatus.innerHTML = ` | |
<div class="status-indicator"></div> | |
<span>خطا در اتصال</span> | |
`; | |
} | |
} | |
} | |
// Check connection status every 30 seconds | |
setInterval(checkConnectionStatus, 30000); | |
// Initial connection check | |
checkConnectionStatus(); | |
// Enhanced Analytics Functions | |
let analyticsCharts = {}; | |
// Analytics API endpoints | |
const ANALYTICS_ENDPOINTS = { | |
realtime: '/api/analytics/realtime', | |
trends: '/api/analytics/trends', | |
predictions: '/api/analytics/predictions', | |
similarity: '/api/analytics/similarity', | |
clustering: '/api/analytics/clustering', | |
quality: '/api/analytics/quality', | |
health: '/api/analytics/health', | |
performance: '/api/analytics/performance' | |
}; | |
// Refresh Overview Analytics | |
async function refreshOverview() { | |
try { | |
showToast('در حال بروزرسانی نمای کلی...', 'info'); | |
const response = await fetch(ANALYTICS_ENDPOINTS.realtime); | |
const data = await response.json(); | |
if (data.status === 'success') { | |
document.getElementById('totalAnalytics').textContent = data.data.total_documents || 0; | |
document.getElementById('activeAnalytics').textContent = data.data.active_users || 0; | |
document.getElementById('accuracyRate').textContent = '85%'; | |
// Update overview chart | |
updateOverviewChart(data.data); | |
showToast('نمای کلی بروزرسانی شد', 'success'); | |
} | |
} catch (error) { | |
console.error('Error refreshing overview:', error); | |
showToast('خطا در بروزرسانی نمای کلی', 'error'); | |
} | |
} | |
// Refresh Trends Analytics | |
async function refreshTrends() { | |
try { | |
showToast('در حال بروزرسانی روندها...', 'info'); | |
const response = await fetch(ANALYTICS_ENDPOINTS.trends); | |
const data = await response.json(); | |
if (data.status === 'success') { | |
updateTrendsChart(data.data); | |
updateTrendsInsights(data.data); | |
showToast('روندها بروزرسانی شد', 'success'); | |
} | |
} catch (error) { | |
console.error('Error refreshing trends:', error); | |
showToast('خطا در بروزرسانی روندها', 'error'); | |
} | |
} | |
// Refresh Predictions Analytics | |
async function refreshPredictions() { | |
try { | |
showToast('در حال بروزرسانی پیشبینیها...', 'info'); | |
const response = await fetch(ANALYTICS_ENDPOINTS.predictions); | |
const data = await response.json(); | |
if (data.status === 'success') { | |
document.getElementById('weeklyForecast').textContent = data.data.predicted_uploads || 0; | |
document.getElementById('confidenceLevel').textContent = `${(data.data.confidence * 100).toFixed(0)}%`; | |
updatePredictionsChart(data.data); | |
showToast('پیشبینیها بروزرسانی شد', 'success'); | |
} | |
} catch (error) { | |
console.error('Error refreshing predictions:', error); | |
showToast('خطا در بروزرسانی پیشبینیها', 'error'); | |
} | |
} | |
// Refresh Quality Analytics | |
async function refreshQuality() { | |
try { | |
showToast('در حال بروزرسانی کیفیت...', 'info'); | |
const response = await fetch(ANALYTICS_ENDPOINTS.quality); | |
const data = await response.json(); | |
if (data.status === 'success') { | |
document.getElementById('overallQuality').textContent = data.data.overall_score || 0; | |
document.getElementById('readabilityScore').textContent = data.data.readability || 0; | |
document.getElementById('accuracyScore').textContent = data.data.accuracy || 0; | |
updateQualityChart(data.data); | |
showToast('کیفیت بروزرسانی شد', 'success'); | |
} | |
} catch (error) { | |
console.error('Error refreshing quality:', error); | |
showToast('خطا در بروزرسانی کیفیت', 'error'); | |
} | |
} | |
// Refresh Health Analytics | |
async function refreshHealth() { | |
try { | |
showToast('در حال بروزرسانی سلامت سیستم...', 'info'); | |
const response = await fetch(ANALYTICS_ENDPOINTS.health); | |
const data = await response.json(); | |
if (data.status === 'success') { | |
document.getElementById('cpuUsage').textContent = `${data.data.cpu_usage || 0}%`; | |
document.getElementById('memoryUsage').textContent = `${data.data.memory_usage || 0}%`; | |
document.getElementById('diskUsage').textContent = `${data.data.disk_usage || 0}%`; | |
updateHealthStatus(data.data); | |
updateHealthChart(data.data); | |
showToast('سلامت سیستم بروزرسانی شد', 'success'); | |
} | |
} catch (error) { | |
console.error('Error refreshing health:', error); | |
showToast('خطا در بروزرسانی سلامت سیستم', 'error'); | |
} | |
} | |
// Refresh Clustering Analytics | |
async function refreshClustering() { | |
try { | |
showToast('در حال بروزرسانی خوشهبندی...', 'info'); | |
const response = await fetch(ANALYTICS_ENDPOINTS.clustering); | |
const data = await response.json(); | |
if (data.status === 'success') { | |
document.getElementById('clusterCount').textContent = data.data.total_clusters || 0; | |
document.getElementById('avgSimilarity').textContent = `${(data.data.avg_similarity * 100).toFixed(0)}%`; | |
updateClusteringChart(data.data); | |
updateClusteringList(data.data); | |
showToast('خوشهبندی بروزرسانی شد', 'success'); | |
} | |
} catch (error) { | |
console.error('Error refreshing clustering:', error); | |
showToast('خطا در بروزرسانی خوشهبندی', 'error'); | |
} | |
} | |
// Refresh Similarity Analytics | |
async function refreshSimilarity() { | |
try { | |
showToast('در حال بروزرسانی شباهت...', 'info'); | |
const response = await fetch(ANALYTICS_ENDPOINTS.similarity); | |
const data = await response.json(); | |
if (data.status === 'success') { | |
updateSimilarityResults(data.data); | |
updateSimilarityChart(data.data); | |
showToast('شباهت بروزرسانی شد', 'success'); | |
} | |
} catch (error) { | |
console.error('Error refreshing similarity:', error); | |
showToast('خطا در بروزرسانی شباهت', 'error'); | |
} | |
} | |
// Chart update functions | |
function updateOverviewChart(data) { | |
const ctx = document.getElementById('overviewChartCanvas'); | |
if (!ctx) return; | |
if (analyticsCharts.overview) { | |
analyticsCharts.overview.destroy(); | |
} | |
analyticsCharts.overview = new Chart(ctx, { | |
type: 'line', | |
data: { | |
labels: ['شنبه', 'یکشنبه', 'دوشنبه', 'سهشنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه'], | |
datasets: [{ | |
label: 'اسناد پردازش شده', | |
data: [12, 19, 8, 15, 22, 18, 14], | |
borderColor: '#3b82f6', | |
backgroundColor: 'rgba(59, 130, 246, 0.1)', | |
tension: 0.4 | |
}] | |
}, | |
options: { | |
responsive: true, | |
maintainAspectRatio: false, | |
plugins: { | |
legend: { | |
display: false | |
} | |
} | |
} | |
}); | |
} | |
function updateTrendsChart(data) { | |
const ctx = document.getElementById('trendsChartCanvas'); | |
if (!ctx) return; | |
if (analyticsCharts.trends) { | |
analyticsCharts.trends.destroy(); | |
} | |
analyticsCharts.trends = new Chart(ctx, { | |
type: 'bar', | |
data: { | |
labels: data.daily_uploads ? data.daily_uploads.map((_, i) => `روز ${i + 1}`) : [], | |
datasets: [{ | |
label: 'آپلود روزانه', | |
data: data.daily_uploads || [12, 15, 8, 20, 18, 22, 16], | |
backgroundColor: '#10b981' | |
}] | |
}, | |
options: { | |
responsive: true, | |
maintainAspectRatio: false | |
} | |
}); | |
} | |
function updatePredictionsChart(data) { | |
const ctx = document.getElementById('predictionsChartCanvas'); | |
if (!ctx) return; | |
if (analyticsCharts.predictions) { | |
analyticsCharts.predictions.destroy(); | |
} | |
analyticsCharts.predictions = new Chart(ctx, { | |
type: 'line', | |
data: { | |
labels: ['هفته 1', 'هفته 2', 'هفته 3', 'هفته 4'], | |
datasets: [{ | |
label: 'پیشبینی', | |
data: data.next_week_forecast || [15, 18, 20, 17], | |
borderColor: '#f59e0b', | |
backgroundColor: 'rgba(245, 158, 11, 0.1)', | |
tension: 0.4 | |
}] | |
}, | |
options: { | |
responsive: true, | |
maintainAspectRatio: false | |
} | |
}); | |
} | |
function updateQualityChart(data) { | |
const ctx = document.getElementById('qualityChartCanvas'); | |
if (!ctx) return; | |
if (analyticsCharts.quality) { | |
analyticsCharts.quality.destroy(); | |
} | |
analyticsCharts.quality = new Chart(ctx, { | |
type: 'radar', | |
data: { | |
labels: ['خوانایی', 'دقت', 'کامل بودن', 'سازگاری'], | |
datasets: [{ | |
label: 'امتیاز کیفیت', | |
data: [data.readability || 7.8, data.accuracy || 8.7, data.completeness || 9.2, 8.5], | |
backgroundColor: 'rgba(59, 130, 246, 0.2)', | |
borderColor: '#3b82f6', | |
pointBackgroundColor: '#3b82f6' | |
}] | |
}, | |
options: { | |
responsive: true, | |
maintainAspectRatio: false, | |
scales: { | |
r: { | |
beginAtZero: true, | |
max: 10 | |
} | |
} | |
} | |
}); | |
} | |
function updateHealthChart(data) { | |
const ctx = document.getElementById('healthChartCanvas'); | |
if (!ctx) return; | |
if (analyticsCharts.health) { | |
analyticsCharts.health.destroy(); | |
} | |
analyticsCharts.health = new Chart(ctx, { | |
type: 'doughnut', | |
data: { | |
labels: ['CPU', 'RAM', 'دیسک'], | |
datasets: [{ | |
data: [data.cpu_usage || 23, data.memory_usage || 45, data.disk_usage || 67], | |
backgroundColor: ['#3b82f6', '#10b981', '#f59e0b'] | |
}] | |
}, | |
options: { | |
responsive: true, | |
maintainAspectRatio: false | |
} | |
}); | |
} | |
function updateClusteringChart(data) { | |
const ctx = document.getElementById('clusteringChartCanvas'); | |
if (!ctx) return; | |
if (analyticsCharts.clustering) { | |
analyticsCharts.clustering.destroy(); | |
} | |
analyticsCharts.clustering = new Chart(ctx, { | |
type: 'pie', | |
data: { | |
labels: data.clusters ? data.clusters.map(c => c.name) : ['خوشه 1', 'خوشه 2', 'خوشه 3'], | |
datasets: [{ | |
data: data.clusters ? data.clusters.map(c => c.size) : [25, 18, 12], | |
backgroundColor: ['#3b82f6', '#10b981', '#f59e0b', '#ef4444'] | |
}] | |
}, | |
options: { | |
responsive: true, | |
maintainAspectRatio: false | |
} | |
}); | |
} | |
// Update functions for insights and data | |
function updateTrendsInsights(data) { | |
const insightsContainer = document.getElementById('trendsInsights'); | |
if (!insightsContainer) return; | |
// Update insights based on data | |
const insights = [ | |
{ icon: 'arrow-up', text: 'افزایش 15% در پردازش اسناد', class: 'text-success' }, | |
{ icon: 'clock', text: 'کاهش زمان پردازش به 2.3 ثانیه', class: 'text-warning' } | |
]; | |
insightsContainer.innerHTML = insights.map(insight => ` | |
<div class="insight-item"> | |
<i class="fas fa-${insight.icon} ${insight.class}"></i> | |
<span>${insight.text}</span> | |
</div> | |
`).join(''); | |
} | |
function updateHealthStatus(data) { | |
const statusElement = document.getElementById('systemStatus'); | |
if (!statusElement) return; | |
const isHealthy = data.system_status === 'healthy'; | |
statusElement.innerHTML = ` | |
<i class="fas fa-circle" style="color: ${isHealthy ? '#10b981' : '#ef4444'}"></i> | |
<span>${isHealthy ? 'سیستم سالم' : 'مشکل در سیستم'}</span> | |
`; | |
} | |
function updateClusteringList(data) { | |
const listContainer = document.getElementById('clusteringList'); | |
if (!listContainer || !data.clusters) return; | |
listContainer.innerHTML = data.clusters.map(cluster => ` | |
<div class="cluster-item"> | |
<div class="cluster-name">${cluster.name}</div> | |
<div class="cluster-size">${cluster.size} سند</div> | |
<div class="cluster-similarity">${(cluster.avg_similarity * 100).toFixed(0)}% شباهت</div> | |
</div> | |
`).join(''); | |
} | |
function updateSimilarityResults(data) { | |
const resultsContainer = document.getElementById('similarityResults'); | |
if (!resultsContainer || !data.similar_documents) return; | |
resultsContainer.innerHTML = data.similar_documents.map(doc => ` | |
<div class="similarity-item"> | |
<div class="doc-title">${doc.title}</div> | |
<div class="doc-similarity">${(doc.similarity * 100).toFixed(0)}% شباهت</div> | |
</div> | |
`).join(''); | |
} | |
function updateSimilarityChart(data) { | |
const ctx = document.getElementById('similarityChartCanvas'); | |
if (!ctx) return; | |
if (analyticsCharts.similarity) { | |
analyticsCharts.similarity.destroy(); | |
} | |
analyticsCharts.similarity = new Chart(ctx, { | |
type: 'bar', | |
data: { | |
labels: data.similar_documents ? data.similar_documents.map(d => d.title) : [], | |
datasets: [{ | |
label: 'درصد شباهت', | |
data: data.similar_documents ? data.similar_documents.map(d => d.similarity * 100) : [], | |
backgroundColor: '#8b5cf6' | |
}] | |
}, | |
options: { | |
responsive: true, | |
maintainAspectRatio: false | |
} | |
}); | |
} | |
// Initialize analytics on page load | |
document.addEventListener('DOMContentLoaded', function() { | |
// Load initial analytics data | |
refreshOverview(); | |
refreshTrends(); | |
refreshPredictions(); | |
refreshQuality(); | |
refreshHealth(); | |
refreshClustering(); | |
refreshSimilarity(); | |
// Set up refresh button | |
const refreshBtn = document.getElementById('refreshAnalyticsBtn'); | |
if (refreshBtn) { | |
refreshBtn.addEventListener('click', function() { | |
refreshOverview(); | |
refreshTrends(); | |
refreshPredictions(); | |
refreshQuality(); | |
refreshHealth(); | |
refreshClustering(); | |
refreshSimilarity(); | |
}); | |
} | |
}); | |
console.log('🚀 Legal Dashboard Main Page Ready!'); | |
</script> | |
</body> | |
</html> |