Hoghoghi / frontend /improved_legal_dashboard.html
Really-amin's picture
Upload 149 files
8d3cb40 verified
raw
history blame
141 kB
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>داشبورد مدیریتی حقوقی | سامانه هوشمند</title>
<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>