Hoghoghi / app /frontend /index.html
Really-amin's picture
Upload 25 files
89ba751 verified
raw
history blame
65.2 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/chart.js@4.4.0/dist/chart.min.js"></script>
<!-- Load API Client and Core System -->
<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>
<script src="js/notifications.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);
/* گرادیان‌های مدرن */
--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);
/* متغیرهای کامپکت */
--sidebar-width: 260px;
--border-radius: 12px;
--border-radius-sm: 8px;
--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;
}
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);
}
/* اسکرول‌بار مدرن */
::-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;
}
/* کانتینر اصلی */
.dashboard-container {
display: flex;
min-block-size: 100vh;
inline-size: 100%;
}
/* سایدبار کامپکت */
.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);
padding: 1rem 0;
position: fixed;
block-size: 100vh;
inset-inline-end: 0;
inset-block-start: 0;
z-index: 1000;
overflow-y: auto;
box-shadow: -8px 0 32px rgba(59, 130, 246, 0.12);
border-inline-start: 1px solid rgba(59, 130, 246, 0.15);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.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);
}
.logo {
display: flex;
align-items: center;
gap: 0.6rem;
color: var(--text-primary);
text-decoration: none;
}
.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;
color: white;
box-shadow: var(--shadow-glow-primary);
}
.logo-text {
font-size: var(--font-size-lg);
font-weight: 700;
background: var(--primary-gradient);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
.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);
}
.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);
font-weight: 500;
font-size: var(--font-size-sm);
cursor: pointer;
border: 1px solid transparent;
}
.nav-link:hover {
color: var(--text-primary);
transform: translateX(-2px);
border-color: rgba(59, 130, 246, 0.15);
background: rgba(59, 130, 246, 0.05);
}
.nav-link.active {
background: var(--primary-gradient);
color: var(--text-light);
box-shadow: var(--shadow-md);
}
.nav-icon {
margin-inline-start: 0.6rem;
inline-size: 1rem;
text-align: center;
font-size: 0.9rem;
}
.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;
}
/* محتوای اصلی */
.main-content {
flex: 1;
margin-inline-end: var(--sidebar-width);
padding: 1rem;
min-block-size: 100vh;
inline-size: calc(100% - var(--sidebar-width));
}
/* هدر کامپکت */
.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;
background-clip: text;
-webkit-text-fill-color: transparent;
display: flex;
align-items: center;
gap: 0.6rem;
}
.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);
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: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);
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);
}
.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);
border-radius: var(--border-radius);
padding: 1.2rem;
box-shadow: var(--shadow-md);
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.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: var(--shadow-lg);
}
.stat-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-block-end: 0.8rem;
}
.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: var(--shadow-sm);
transition: var(--transition-smooth);
}
.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;
}
.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);
border-radius: var(--border-radius);
padding: 1.5rem;
box-shadow: var(--shadow-md);
border: 1px solid rgba(255, 255, 255, 0.3);
transition: var(--transition-smooth);
}
.chart-card:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-lg);
}
.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;
}
/* دسترسی سریع */
.quick-access-section {
margin-block-end: 1.5rem;
}
.quick-access-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1rem;
padding: 1rem 0;
}
.quick-access-item {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem;
background: rgba(255, 255, 255, 0.7);
border-radius: var(--border-radius-sm);
text-decoration: none;
color: var(--text-primary);
transition: var(--transition-smooth);
border: 1px solid rgba(59, 130, 246, 0.1);
position: relative;
overflow: hidden;
}
.quick-access-item::before {
content: '';
position: absolute;
inset-block-start: 0;
inset-inline-start: 0;
inset-block-end: 0;
inline-size: 4px;
background: var(--primary-gradient);
opacity: 0;
transition: var(--transition-smooth);
}
.quick-access-item:hover {
background: rgba(255, 255, 255, 0.9);
transform: translateY(-2px);
box-shadow: var(--shadow-md);
border-color: rgba(59, 130, 246, 0.3);
}
.quick-access-item:hover::before {
opacity: 1;
}
.quick-access-icon {
inline-size: 3rem;
block-size: 3rem;
background: var(--primary-gradient);
border-radius: var(--border-radius-sm);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1.2rem;
flex-shrink: 0;
box-shadow: var(--shadow-sm);
transition: var(--transition-smooth);
}
.quick-access-item:hover .quick-access-icon {
transform: scale(1.1);
box-shadow: var(--shadow-md);
}
.quick-access-content h3 {
font-size: var(--font-size-base);
font-weight: 600;
color: var(--text-primary);
margin-block-end: 0.3rem;
}
.quick-access-content p {
font-size: var(--font-size-sm);
color: var(--text-secondary);
margin: 0;
}
/* 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;
}
/* واکنش‌گرایی */
@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%;
padding: 1rem;
}
.dashboard-header {
flex-direction: column;
align-items: flex-start;
gap: 0.8rem;
}
.header-actions {
width: 100%;
justify-content: space-between;
flex-direction: column;
gap: 0.8rem;
}
.search-container {
inline-size: 100%;
}
.search-input {
inline-size: 100%;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
}
.charts-section {
grid-template-columns: 1fr;
}
}
@media (max-inline-size: 768px) {
.main-content {
padding: 0.8rem;
}
.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;
}
}
</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>
</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">
<i class="fas fa-chart-pie nav-icon"></i>
<span>نمای کلی</span>
</a>
</li>
<li class="nav-item">
<a href="enhanced_analytics_dashboard.html" class="nav-link">
<i class="fas fa-chart-area 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">
<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">
<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">
<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">
<i class="fas fa-globe nav-icon"></i>
<span>استخراج محتوا</span>
</a>
</li>
<li class="nav-item">
<a href="scraping_dashboard.html" class="nav-link">
<i class="fas fa-spider nav-icon"></i>
<span>داشبورد اسکرپینگ</span>
</a>
</li>
<li class="nav-item">
<a href="analytics.html" class="nav-link">
<i class="fas fa-chart-line nav-icon"></i>
<span>آمار و تحلیل</span>
</a>
</li>
<li class="nav-item">
<a href="reports.html" class="nav-link">
<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">
<i class="fas fa-cog nav-icon"></i>
<span>تنظیمات</span>
</a>
</li>
<li class="nav-item">
<a href="dev/api-test.html" class="nav-link">
<i class="fas fa-code nav-icon"></i>
<span>تست API</span>
</a>
</li>
<li class="nav-item">
<a href="#" class="nav-link">
<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"></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">
<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">
<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">
<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">
<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">
<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">
<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>
<!-- دسترسی سریع -->
<section class="quick-access-section">
<div class="chart-card">
<div class="chart-header">
<h2 class="chart-title">
<i class="fas fa-bolt"></i>
دسترسی سریع
</h2>
</div>
<div class="quick-access-grid">
<a href="upload.html" class="quick-access-item">
<div class="quick-access-icon">
<i class="fas fa-cloud-upload-alt"></i>
</div>
<div class="quick-access-content">
<h3>آپلود سند جدید</h3>
<p>آپلود و پردازش اسناد PDF</p>
</div>
</a>
<a href="documents.html" class="quick-access-item">
<div class="quick-access-icon">
<i class="fas fa-folder-open"></i>
</div>
<div class="quick-access-content">
<h3>مدیریت اسناد</h3>
<p>مشاهده و ویرایش اسناد</p>
</div>
</a>
<a href="search.html" class="quick-access-item">
<div class="quick-access-icon">
<i class="fas fa-search"></i>
</div>
<div class="quick-access-content">
<h3>جستجو در اسناد</h3>
<p>جستجوی هوشمند در محتوا</p>
</div>
</a>
<a href="scraping.html" class="quick-access-item">
<div class="quick-access-icon">
<i class="fas fa-globe"></i>
</div>
<div class="quick-access-content">
<h3>استخراج از وب</h3>
<p>دریافت محتوا از وب‌سایت‌ها</p>
</div>
</a>
<a href="analytics.html" class="quick-access-item">
<div class="quick-access-icon">
<i class="fas fa-chart-line"></i>
</div>
<div class="quick-access-content">
<h3>آمار و تحلیل</h3>
<p>تحلیل عملکرد و آمار</p>
</div>
</a>
<a href="reports.html" class="quick-access-item">
<div class="quick-access-icon">
<i class="fas fa-file-export"></i>
</div>
<div class="quick-access-content">
<h3>گزارش‌گیری</h3>
<p>تولید گزارش‌های تفصیلی</p>
</div>
</a>
</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>
// Global variables
let documentsChart = null;
let statusChart = null;
let chartJsLoaded = false;
let isOnline = false;
// 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'
};
// Initialize when page loads
document.addEventListener('DOMContentLoaded', function() {
console.log('🏠 Dashboard loading...');
initializeDashboard();
});
async function initializeDashboard() {
try {
// Test connection first
isOnline = await testConnection();
// Setup Chart.js loading check
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();
await loadInitialData();
showToast('داشبورد با موفقیت بارگذاری شد', 'success', 'خوش آمدید');
} catch (error) {
console.error('Failed to initialize dashboard:', error);
isOnline = false;
setupEventListeners();
showToast('حالت آفلاین فعال است', 'warning', 'اتصال ناموفق');
}
}
async function testConnection() {
try {
// Try to connect to API if available
if (window.legalAPI && window.legalAPI.healthCheck) {
await window.legalAPI.healthCheck();
return true;
} else {
// Fallback to simple fetch
const response = await fetch(API_ENDPOINTS.health);
return response.ok;
}
} catch (error) {
console.log('API connection failed, using offline mode');
return false;
}
}
// 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 {
showToast('در حال بارگذاری داده‌ها...', 'info');
if (isOnline) {
// Try to load real data from API
await Promise.all([
loadDashboardStats(),
loadChartsData(),
updateDocumentsBadge()
]);
showToast('داده‌ها از سرور بارگذاری شدند', 'success');
} else {
// Use mock data in offline mode
loadMockData();
showToast('داده‌های آزمایشی بارگذاری شدند', 'info');
}
} catch (error) {
console.error('Error loading initial data:', error);
// Fallback to mock data
loadMockData();
showToast('خطا در بارگذاری - از داده‌های آزمایشی استفاده شد', 'warning');
}
}
// Load dashboard statistics from API
async function loadDashboardStats() {
try {
const response = await fetch(API_ENDPOINTS.dashboardSummary);
if (!response.ok) throw new Error('Failed to load dashboard stats');
const stats = await response.json();
// 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);
console.log('Dashboard stats loaded from API');
} catch (error) {
console.error('Failed to load dashboard stats:', error);
throw error;
}
}
// Load charts data from API
async function loadChartsData() {
try {
const response = await fetch(API_ENDPOINTS.chartsData);
if (!response.ok) throw new Error('Failed to load charts data');
const chartsData = await response.json();
console.log('Charts data loaded from API');
return chartsData;
} catch (error) {
console.error('Failed to load charts data:', error);
throw error;
}
}
// Update documents badge
async function updateDocumentsBadge() {
try {
const response = await fetch(API_ENDPOINTS.documents);
if (!response.ok) throw new Error('Failed to load documents count');
const data = await response.json();
const badge = document.getElementById('totalDocumentsBadge');
if (badge && data.total_count !== undefined) {
badge.textContent = data.total_count;
}
} catch (error) {
console.error('Failed to update documents badge:', error);
throw error;
}
}
// Load mock data for offline mode
function loadMockData() {
// Update stats with mock data
document.getElementById('totalDocuments').textContent = '6';
document.getElementById('processedDocuments').textContent = '4';
document.getElementById('errorDocuments').textContent = '1';
document.getElementById('averageQuality').textContent = '8.1';
// Update badge
const badge = document.getElementById('totalDocumentsBadge');
if (badge) {
badge.textContent = '6';
}
}
// 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', 'بروزرسانی');
}
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 = response.ok;
const connectionStatus = document.getElementById('connectionStatus');
if (connectionStatus) {
if (status) {
connectionStatus.className = 'connection-status online';
connectionStatus.innerHTML = `
<div class="status-indicator"></div>
<span>متصل به سرور</span>
`;
// Update online status and refresh data if needed
if (!isOnline) {
isOnline = true;
loadInitialData(); // Refresh with real data
}
} else {
throw new Error('Server not responding');
}
}
} catch (error) {
const connectionStatus = document.getElementById('connectionStatus');
if (connectionStatus) {
connectionStatus.className = 'connection-status offline';
connectionStatus.innerHTML = `
<div class="status-indicator"></div>
<span>خطا در اتصال</span>
`;
// Update offline status
if (isOnline) {
isOnline = false;
showToast('اتصال قطع شد - حالت آفلاین فعال', 'warning', 'اتصال');
}
}
}
}
// Check connection status every 30 seconds
setInterval(checkConnectionStatus, 30000);
// Initial connection check after 2 seconds
setTimeout(checkConnectionStatus, 2000);
console.log('🏠 Legal Dashboard Index Page Ready!');
</script>
</body>
</html>