Really-amin commited on
Commit
bca8cda
·
verified ·
1 Parent(s): df7e4aa

Update frontend/index.html

Browse files
Files changed (1) hide show
  1. frontend/index.html +287 -1087
frontend/index.html CHANGED
@@ -1,4 +1,3 @@
1
- <!DOCTYPE html>
2
  <html lang="fa" dir="rtl">
3
  <head>
4
  <meta charset="UTF-8">
@@ -771,6 +770,72 @@
771
  100% { transform: rotate(360deg); }
772
  }
773
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
774
  /* واکنش‌گرایی */
775
  @media (max-width: 992px) {
776
  .mobile-menu-toggle {
@@ -850,7 +915,6 @@
850
  </head>
851
  <body>
852
  <div class="dashboard-container">
853
- <!-- سایدبار راست -->
854
  <aside class="sidebar" id="sidebar">
855
  <div class="sidebar-header">
856
  <div class="logo">
@@ -883,6 +947,12 @@
883
  <span>آنالیتیکس پیشرفته</span>
884
  </a>
885
  </li>
 
 
 
 
 
 
886
  </ul>
887
  </div>
888
 
@@ -930,7 +1000,7 @@
930
  </div>
931
 
932
  <div class="nav-section">
933
- <h6 class="nav-title">گزارشات</h6>
934
  <ul class="nav-menu">
935
  <li class="nav-item">
936
  <a href="reports.html" class="nav-link">
@@ -938,11 +1008,17 @@
938
  <span>گزارشات</span>
939
  </a>
940
  </li>
 
 
 
 
 
 
941
  </ul>
942
  </div>
943
 
944
  <div class="nav-section">
945
- <h6 class="nav-title">تنظیمات</h6>
946
  <ul class="nav-menu">
947
  <li class="nav-item">
948
  <a href="settings.html" class="nav-link">
@@ -964,30 +1040,10 @@
964
  </li>
965
  </ul>
966
  </div>
967
-
968
- <div class="nav-section">
969
- <h6 class="nav-title">توسعه</h6>
970
- <ul class="nav-menu">
971
- <li class="nav-item">
972
- <a href="dev/real-api-test.html" class="nav-link">
973
- <i class="fas fa-flask nav-icon"></i>
974
- <span>تست API</span>
975
- </a>
976
- </li>
977
- <li class="nav-item">
978
- <a href="dev/comprehensive-test.html" class="nav-link">
979
- <i class="fas fa-vials nav-icon"></i>
980
- <span>تست جامع</span>
981
- </a>
982
- </li>
983
- </ul>
984
- </div>
985
  </nav>
986
  </aside>
987
 
988
- <!-- محتوای اصلی -->
989
  <main class="main-content" id="mainContent">
990
- <!-- هدر -->
991
  <header class="dashboard-header">
992
  <div>
993
  <h1 class="dashboard-title">
@@ -995,17 +1051,14 @@
995
  <span>داشبورد مدیریتی حقوقی</span>
996
  </h1>
997
  </div>
998
-
999
  <div class="header-actions">
1000
  <button type="button" class="mobile-menu-toggle" id="mobileMenuToggle" aria-label="منوی موبایل">
1001
  <i class="fas fa-bars"></i>
1002
  </button>
1003
-
1004
  <div class="search-container">
1005
  <input type="text" class="search-input" id="searchInput" placeholder="جستجو در سیستم...">
1006
  <i class="fas fa-search search-icon"></i>
1007
  </div>
1008
-
1009
  <div class="user-profile">
1010
  <div class="user-avatar">ح</div>
1011
  <div class="user-info">
@@ -1017,7 +1070,6 @@
1017
  </div>
1018
  </header>
1019
 
1020
- <!-- کارت‌های آمار -->
1021
  <section aria-labelledby="stats-section">
1022
  <h2 id="stats-section" class="sr-only">آمار و ارقام کلیدی</h2>
1023
  <div class="stats-grid">
@@ -1039,7 +1091,6 @@
1039
  </div>
1040
  </div>
1041
  </div>
1042
-
1043
  <div class="stat-card success">
1044
  <div class="stat-header">
1045
  <div class="stat-content">
@@ -1058,7 +1109,6 @@
1058
  </div>
1059
  </div>
1060
  </div>
1061
-
1062
  <div class="stat-card warning">
1063
  <div class="stat-header">
1064
  <div class="stat-content">
@@ -1077,7 +1127,6 @@
1077
  </div>
1078
  </div>
1079
  </div>
1080
-
1081
  <div class="stat-card danger">
1082
  <div class="stat-header">
1083
  <div class="stat-content">
@@ -1099,7 +1148,6 @@
1099
  </div>
1100
  </section>
1101
 
1102
- <!-- نمودارها -->
1103
  <section class="charts-section">
1104
  <div class="chart-card">
1105
  <div class="chart-header">
@@ -1118,1117 +1166,269 @@
1118
  </div>
1119
  </div>
1120
  </div>
1121
-
1122
  <div class="chart-card">
1123
  <div class="chart-header">
1124
- <h2 class="chart-title">وضعیت سرویس‌ها</h2>
1125
- <div class="chart-filters">
1126
- <button type="button" class="chart-filter active" onclick="updateStatusChart('services')">سرویس‌ها</button>
1127
- <button type="button" class="chart-filter" onclick="updateStatusChart('endpoints')">API ها</button>
1128
- </div>
1129
- </div>
1130
- <div class="chart-container" id="statusChart">
1131
- <canvas id="statusChartCanvas"></canvas>
1132
- <div class="chart-placeholder" style="display: none;">
1133
- <i class="fas fa-chart-pie"></i>
1134
- <p>در حال بارگذاری نمودار...</p>
1135
- </div>
1136
  </div>
 
 
1137
  </div>
1138
  </section>
1139
 
1140
- <!-- آپلود فایل -->
1141
  <section class="quick-access-section">
1142
- <div class="chart-card">
1143
- <div class="chart-header">
1144
- <h2 class="chart-title">
1145
- <i class="fas fa-cloud-upload-alt"></i>
1146
- آپلود و پردازش فایل
1147
- </h2>
1148
- </div>
1149
-
1150
- <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; margin-bottom: 2rem;">
1151
- <!-- PDF Upload -->
1152
- <div style="border: 2px dashed #3b82f6; border-radius: 12px; padding: 2rem; text-align: center; background: rgba(59, 130, 246, 0.05);">
1153
- <div style="margin-bottom: 1rem;">
1154
- <i class="fas fa-file-pdf" style="font-size: 3rem; color: #ef4444; margin-bottom: 1rem;"></i>
1155
- <h3>آپلود فایل PDF</h3>
1156
- <p style="color: var(--text-secondary); margin-bottom: 1rem;">فایل PDF خود را برای استخراج متن آپلود کنید</p>
1157
- </div>
1158
- <input type="file" id="pdfFileInput" accept=".pdf" style="display: none;" onchange="handlePDFUpload(event)">
1159
- <button onclick="document.getElementById('pdfFileInput').click()"
1160
- style="background: var(--danger-gradient); color: white; border: none; padding: 0.8rem 1.5rem; border-radius: 8px; cursor: pointer; font-weight: 600;">
1161
- <i class="fas fa-file-pdf"></i> انتخاب فایل PDF
1162
- </button>
1163
  </div>
1164
-
1165
- <!-- Image Upload -->
1166
- <div style="border: 2px dashed #10b981; border-radius: 12px; padding: 2rem; text-align: center; background: rgba(16, 185, 129, 0.05);">
1167
- <div style="margin-bottom: 1rem;">
1168
- <i class="fas fa-image" style="font-size: 3rem; color: #10b981; margin-bottom: 1rem;"></i>
1169
- <h3>آپلود تصویر</h3>
1170
- <p style="color: var(--text-secondary); margin-bottom: 1rem;">تصویر سند را برای OCR آپلود کنید</p>
1171
- </div>
1172
- <input type="file" id="imageFileInput" accept=".jpg,.jpeg,.png,.bmp,.tiff" style="display: none;" onchange="handleImageUpload(event)">
1173
- <button onclick="document.getElementById('imageFileInput').click()"
1174
- style="background: var(--success-gradient); color: white; border: none; padding: 0.8rem 1.5rem; border-radius: 8px; cursor: pointer; font-weight: 600;">
1175
- <i class="fas fa-image"></i> انتخاب تصویر
1176
- </button>
1177
  </div>
1178
- </div>
1179
-
1180
- <!-- Results Area -->
1181
- <div id="resultsArea" style="display: none; background: var(--glass-bg); border-radius: 12px; padding: 1.5rem; margin-top: 1rem;">
1182
- <h3 style="margin-bottom: 1rem; color: var(--text-primary);">
1183
- <i class="fas fa-file-alt"></i> نتیجه استخراج متن
1184
- </h3>
1185
- <div id="extractedText" style="background: white; border: 1px solid #e2e8f0; border-radius: 8px; padding: 1rem; min-height: 200px; font-family: 'Courier New', monospace; white-space: pre-wrap; overflow-y: auto; max-height: 400px;">
1186
  </div>
1187
- <div style="margin-top: 1rem; display: flex; gap: 1rem;">
1188
- <button onclick="copyToClipboard()" style="background: var(--primary-gradient); color: white; border: none; padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer;">
1189
- <i class="fas fa-copy"></i> کپی متن
1190
- </button>
1191
- <button onclick="clearResults()" style="background: var(--text-secondary); color: white; border: none; padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer;">
1192
- <i class="fas fa-trash"></i> پاک کردن
1193
- </button>
1194
  </div>
1195
- </div>
1196
- </div>
1197
- </section>
1198
-
1199
- <!-- دسترسی سریع -->
1200
- <section class="quick-access-section">
1201
- <div class="chart-card">
1202
- <div class="chart-header">
1203
- <h2 class="chart-title">
1204
- <i class="fas fa-bolt"></i>
1205
- دسترسی سریع
1206
- </h2>
1207
- </div>
1208
- <div class="quick-access-grid">
1209
- <a href="upload.html" class="quick-access-item">
1210
- <div class="quick-access-icon">
1211
- <i class="fas fa-cloud-upload-alt"></i>
1212
- </div>
1213
- <div class="quick-access-content">
1214
- <h3>آپلود فایل</h3>
1215
- <p>آپلود و پردازش اسناد جدید</p>
1216
- </div>
1217
- </a>
1218
-
1219
- <a href="documents.html" class="quick-access-item">
1220
- <div class="quick-access-icon">
1221
- <i class="fas fa-folder-open"></i>
1222
- </div>
1223
- <div class="quick-access-content">
1224
- <h3>مدیریت اسناد</h3>
1225
- <p>مشاهده و مدیریت فایل‌های آپلود شده</p>
1226
- </div>
1227
- </a>
1228
-
1229
- <a href="search.html" class="quick-access-item">
1230
- <div class="quick-access-icon">
1231
- <i class="fas fa-search"></i>
1232
- </div>
1233
- <div class="quick-access-content">
1234
- <h3>جستجو در اسناد</h3>
1235
- <p>جستجوی پیشرفته در محتوای اسناد</p>
1236
- </div>
1237
- </a>
1238
-
1239
- <a href="scraping.html" class="quick-access-item">
1240
- <div class="quick-access-icon">
1241
- <i class="fas fa-spider"></i>
1242
- </div>
1243
- <div class="quick-access-content">
1244
- <h3>وب اسکرپینگ</h3>
1245
- <p>جمع‌آوری داده از وب‌سایت‌ها</p>
1246
- </div>
1247
- </a>
1248
-
1249
- <a href="analytics.html" class="quick-access-item">
1250
- <div class="quick-access-icon">
1251
- <i class="fas fa-chart-line"></i>
1252
- </div>
1253
- <div class="quick-access-content">
1254
- <h3>آنالیتیکس</h3>
1255
- <p>تحلیل داده‌ها و گزارش‌های آماری</p>
1256
- </div>
1257
- </a>
1258
-
1259
- <a href="reports.html" class="quick-access-item">
1260
- <div class="quick-access-icon">
1261
- <i class="fas fa-file-alt"></i>
1262
- </div>
1263
- <div class="quick-access-content">
1264
- <h3>گزارشات</h3>
1265
- <p>تولید و مشاهده گزارش‌های سیستم</p>
1266
- </div>
1267
- </a>
1268
- </div>
1269
  </div>
1270
  </section>
1271
  </main>
1272
  </div>
1273
 
1274
- <!-- Toast Container -->
1275
  <div class="toast-container" id="toastContainer"></div>
1276
 
1277
- <!-- Connection Status -->
1278
- <div class="connection-status offline" id="connectionStatus">
1279
- <div class="status-indicator"></div>
1280
- <span>در حال اتصال...</span>
1281
  </div>
1282
 
1283
- <!-- Load JavaScript Files -->
1284
- <script src="js/notifications.js"></script>
1285
- <script src="js/api-client.js"></script>
1286
- <script src="js/core.js"></script>
1287
- <script src="js/file-upload-handler.js"></script>
1288
- <script src="js/document-crud.js"></script>
1289
- <script src="js/scraping-control.js"></script>
1290
-
1291
  <script>
1292
- // Global Dashboard Controller - Enhanced Version
1293
- class EnhancedDashboardController {
1294
- constructor() {
1295
- this.apiClient = null;
1296
- this.isOnline = false;
1297
- this.systemHealth = {};
1298
- this.lastExtractedText = '';
1299
- this.realTimeData = {
1300
- documents: 0,
1301
- processing: 0,
1302
- success: 0,
1303
- errors: 0
1304
- };
1305
- this.charts = {
1306
- performance: null,
1307
- status: null
1308
- };
1309
- this.isChartJSLoaded = false;
1310
- this.updateInterval = null;
1311
-
1312
- // Initialize when DOM is ready
1313
- if (document.readyState === 'loading') {
1314
- document.addEventListener('DOMContentLoaded', () => this.initialize());
1315
- } else {
1316
- this.initialize();
1317
  }
1318
- }
1319
-
1320
- async initialize() {
1321
- console.log('🏠 Enhanced Dashboard initializing...');
1322
-
1323
- try {
1324
- // Initialize API Client (use global instance if available)
1325
- this.apiClient = window.legalAPI || new LegalDashboardAPI();
1326
-
1327
- // Initialize core dashboard
1328
- if (window.dashboardCore) {
1329
- this.setupCoreEventListeners();
1330
- }
1331
-
1332
- // Initialize notifications
1333
- if (window.notificationManager) {
1334
- window.notificationManager.showInfo('سیستم آماده شد', 'داشبورد با موفقیت بارگذاری شد');
1335
- }
1336
-
1337
- // Setup Chart.js
1338
- this.waitForChartJS();
1339
-
1340
- // Setup event listeners
1341
- this.setupEventListeners();
1342
-
1343
- // Load real data
1344
- await this.loadDashboardData();
1345
-
1346
- // Start real-time updates
1347
- this.startRealTimeUpdates();
1348
-
1349
- console.log('✅ Enhanced Dashboard initialized successfully');
1350
-
1351
- } catch (error) {
1352
- console.error('❌ Dashboard initialization failed:', error);
1353
- this.showFallbackMode();
1354
- }
1355
- }
1356
-
1357
- setupCoreEventListeners() {
1358
- // Listen for document uploads
1359
- window.dashboardCore.listen('documentUploaded', (data) => {
1360
- this.handleDocumentEvent('uploaded', data);
1361
- });
1362
-
1363
- // Listen for document updates
1364
- window.dashboardCore.listen('documentUpdated', (data) => {
1365
- this.handleDocumentEvent('updated', data);
1366
- });
1367
-
1368
- // Listen for scraping updates
1369
- window.dashboardCore.listen('scrapingUpdate', (data) => {
1370
- this.handleScrapingUpdate(data);
1371
- });
1372
 
1373
- // Listen for health updates
1374
- window.dashboardCore.listen('healthUpdate', (data) => {
1375
- this.updateSystemHealth(data);
 
1376
  });
1377
- }
1378
-
1379
- async loadDashboardData() {
1380
- try {
1381
- // Load dashboard summary
1382
- const summary = await this.apiClient.getDashboardSummary();
1383
- this.updateStatsFromSummary(summary);
1384
-
1385
- // Load system health
1386
- const health = await this.apiClient.healthCheck();
1387
- this.updateSystemHealth(health);
1388
-
1389
- // Load recent documents count
1390
- const documents = await this.apiClient.getDocuments({ limit: 1 });
1391
- this.realTimeData.documents = documents.total || 0;
1392
-
1393
- console.log('📊 Dashboard data loaded successfully');
1394
- } catch (error) {
1395
- console.error('Failed to load dashboard data:', error);
1396
- this.loadFallbackData();
1397
- }
1398
- }
1399
-
1400
- loadFallbackData() {
1401
- // Use mock data when API is not available
1402
- this.realTimeData = {
1403
- documents: 847,
1404
- processing: 12,
1405
- success: 98.5,
1406
- errors: 3
1407
- };
1408
-
1409
- this.systemHealth = {
1410
- status: 'healthy',
1411
- services: {
1412
- ocr_model_loaded: true,
1413
- pdf_processing: true,
1414
- cache_active: true
1415
- }
1416
- };
1417
-
1418
- this.updateHealthStats(this.systemHealth, 180);
1419
- console.log('📊 Using fallback data');
1420
- }
1421
-
1422
- updateStatsFromSummary(summary) {
1423
- if (!summary) return;
1424
-
1425
- // Update document count
1426
- const docCount = summary.total_documents || this.realTimeData.documents;
1427
- this.updateStatCard('documentsCount', 'documentsChange',
1428
- docCount.toLocaleString('fa-IR'),
1429
- { icon: 'fas fa-arrow-up', text: `${summary.documents_today || 0} امروز`, type: 'positive' }
1430
- );
1431
-
1432
- // Update processing stats if available
1433
- if (summary.processing_stats) {
1434
- this.realTimeData.processing = summary.processing_stats.in_progress || 0;
1435
- this.realTimeData.success = summary.processing_stats.success_rate || 0;
1436
- }
1437
- }
1438
-
1439
- handleDocumentEvent(eventType, data) {
1440
- switch (eventType) {
1441
- case 'uploaded':
1442
- this.realTimeData.documents++;
1443
- this.updateDocumentStats();
1444
- if (window.notificationManager) {
1445
- window.notificationManager.showSuccess('آپلود موفق', `فایل ${data.fileName} آپلود شد`);
1446
- }
1447
- break;
1448
- case 'updated':
1449
- this.updateDocumentStats();
1450
- break;
1451
- }
1452
-
1453
- // Refresh charts with new data
1454
- this.updateChartsWithRealData();
1455
- }
1456
-
1457
- handleScrapingUpdate(data) {
1458
- // Update scraping status indicator
1459
- const scrapingIndicator = document.querySelector('.scraping-status');
1460
- if (scrapingIndicator) {
1461
- scrapingIndicator.textContent = data.status;
1462
- scrapingIndicator.className = `scraping-status ${data.status}`;
1463
- }
1464
-
1465
- if (window.notificationManager) {
1466
- window.notificationManager.showInfo('وضعیت اسکرپینگ', `وضعیت: ${data.status}`);
1467
- }
1468
- }
1469
 
1470
- updateDocumentStats() {
1471
- this.updateStatCard('documentsCount', 'documentsChange',
1472
- this.realTimeData.documents.toLocaleString('fa-IR'),
1473
- { icon: 'fas fa-arrow-up', text: 'به‌روزرسانی شد', type: 'positive' }
1474
- );
1475
  }
1476
 
1477
- startRealTimeUpdates() {
1478
- // Update every 30 seconds
1479
- this.updateInterval = setInterval(async () => {
1480
- try {
1481
- await this.checkSystemHealth();
1482
- await this.updateRealTimeStats();
1483
- } catch (error) {
1484
- console.error('Real-time update failed:', error);
1485
- }
1486
- }, 30000);
1487
- }
1488
-
1489
- async updateRealTimeStats() {
1490
- try {
1491
- // Get latest stats from API
1492
- const summary = await this.apiClient.getDashboardSummary();
1493
- if (summary) {
1494
- this.updateStatsFromSummary(summary);
1495
- }
1496
- } catch (error) {
1497
- // Simulate some changes for demo
1498
- this.simulateDataChanges();
1499
- }
1500
- }
1501
-
1502
- simulateDataChanges() {
1503
- // Add some randomness to show live updates
1504
- const change = Math.floor(Math.random() * 3) - 1; // -1, 0, or 1
1505
- if (change !== 0) {
1506
- this.realTimeData.documents += change;
1507
- this.updateDocumentStats();
1508
- }
1509
- }
1510
-
1511
- updateChartsWithRealData() {
1512
- if (!this.isChartJSLoaded || !this.charts.performance) return;
1513
-
1514
- // Update performance chart with real data
1515
- const currentHour = new Date().getHours();
1516
- const responseTimeData = this.generateRealisticResponseTimes();
1517
- const cpuUsageData = this.generateRealisticCPUUsage();
1518
-
1519
- this.charts.performance.data.datasets[0].data = responseTimeData;
1520
- this.charts.performance.data.datasets[1].data = cpuUsageData;
1521
- this.charts.performance.update('none'); // Smooth update
1522
- }
1523
-
1524
- generateRealisticResponseTimes() {
1525
- // Generate realistic response times based on current system load
1526
- const baseTime = 120;
1527
- const variance = 50;
1528
- return Array.from({length: 7}, () =>
1529
- Math.max(50, baseTime + Math.random() * variance - variance/2)
1530
- );
1531
- }
1532
-
1533
- generateRealisticCPUUsage() {
1534
- // Generate realistic CPU usage
1535
- const baseUsage = 30;
1536
- const variance = 20;
1537
- return Array.from({length: 7}, () =>
1538
- Math.max(10, Math.min(90, baseUsage + Math.random() * variance - variance/2))
1539
- );
1540
- }
1541
-
1542
- showFallbackMode() {
1543
- this.loadFallbackData();
1544
- this.setupEventListeners();
1545
- if (window.notificationManager) {
1546
- window.notificationManager.showWarning('حالت آفلاین', 'اتصال به سرور برقرار نشد - از داده‌های محلی استفاده می‌شود');
1547
  }
1548
  }
1549
 
1550
- // Rest of the methods remain the same but enhanced...
1551
- waitForChartJS() {
1552
- let attempts = 0;
1553
- const maxAttempts = 20;
1554
-
1555
- const checkInterval = setInterval(() => {
1556
- attempts++;
1557
-
1558
- if (typeof Chart !== 'undefined') {
1559
- this.isChartJSLoaded = true;
1560
- clearInterval(checkInterval);
1561
- console.log('✅ Chart.js loaded');
1562
- this.initializeCharts();
1563
- if (window.notificationManager) {
1564
- window.notificationManager.showSuccess('نمودارها آماده', 'Chart.js بارگذاری شد');
1565
- }
1566
- } else if (attempts >= maxAttempts) {
1567
- clearInterval(checkInterval);
1568
- console.warn('⚠️ Chart.js failed to load');
1569
- this.showChartPlaceholders();
1570
- if (window.notificationManager) {
1571
- window.notificationManager.showWarning('خطا در نمودارها', 'Chart.js بارگذاری نشد');
1572
- }
1573
- }
1574
- }, 500);
1575
  }
1576
 
1577
- async checkSystemHealth() {
1578
- const startTime = Date.now();
1579
-
1580
- try {
1581
- const healthData = await this.apiClient.healthCheck();
1582
- const responseTime = Date.now() - startTime;
1583
-
1584
- this.isOnline = true;
1585
- this.systemHealth = healthData;
1586
-
1587
- this.updateConnectionStatus(true);
1588
- this.updateHealthStats(healthData, responseTime);
1589
-
1590
- } catch (error) {
1591
- console.error('Health check failed:', error);
1592
- this.isOnline = false;
1593
- this.updateConnectionStatus(false);
1594
- this.updateHealthStats(null, null);
1595
- }
1596
  }
1597
 
1598
- updateConnectionStatus(online) {
1599
- const status = document.getElementById('connectionStatus');
1600
- if (status) {
1601
- if (online) {
1602
- status.className = 'connection-status online';
1603
- status.innerHTML = `
1604
- <div class="status-indicator"></div>
1605
- <span>متصل به سرور</span>
1606
- `;
1607
- } else {
1608
- status.className = 'connection-status offline';
1609
- status.innerHTML = `
1610
- <div class="status-indicator"></div>
1611
- <span>خطا در اتصال</span>
1612
- `;
1613
- }
1614
- }
1615
- }
1616
-
1617
- updateHealthStats(data, responseTime) {
1618
- // API Status
1619
- this.updateStatCard('apiStatus', 'apiChange',
1620
- data ? '✅ آنلاین' : '❌ آفلاین',
1621
- data ? { icon: 'fas fa-arrow-up', text: 'فعال', type: 'positive' } :
1622
- { icon: 'fas fa-arrow-down', text: 'قطع', type: 'negative' }
1623
- );
1624
-
1625
- // OCR Status
1626
- const ocrReady = data && data.services && data.services.ocr_model_loaded;
1627
- this.updateStatCard('ocrStatus', 'ocrChange',
1628
- ocrReady ? '✅ آماده' : data ? '⏳ بارگذاری' : '❌ خطا',
1629
- ocrReady ? { icon: 'fas fa-check', text: 'بارگذاری شده', type: 'positive' } :
1630
- data ? { icon: 'fas fa-clock', text: 'در حال آماده‌سازی', type: '' } :
1631
- { icon: 'fas fa-times', text: 'غیرفعال', type: 'negative' }
1632
- );
1633
-
1634
- // Response Time
1635
- if (responseTime !== null) {
1636
- this.updateStatCard('responseTime', 'timeChange',
1637
- `${responseTime}ms`,
1638
- responseTime < 500 ? { icon: 'fas fa-arrow-up', text: 'سریع', type: 'positive' } :
1639
- responseTime < 1000 ? { icon: 'fas fa-minus', text: 'متوسط', type: '' } :
1640
- { icon: 'fas fa-arrow-down', text: 'کند', type: 'negative' }
1641
- );
1642
- } else {
1643
- this.updateStatCard('responseTime', 'timeChange', 'N/A',
1644
- { icon: 'fas fa-times', text: 'خطا', type: 'negative' }
1645
- );
1646
- }
1647
- }
1648
 
1649
- updateStatCard(valueId, changeId, value, change) {
1650
- const valueEl = document.getElementById(valueId);
1651
- const changeEl = document.getElementById(changeId);
1652
-
1653
- if (valueEl) valueEl.innerHTML = value;
1654
- if (changeEl) {
1655
- changeEl.innerHTML = `<i class="${change.icon}"></i><span>${change.text}</span>`;
1656
- changeEl.className = `stat-change ${change.type}`;
1657
  }
1658
- }
1659
-
1660
- initializeCharts() {
1661
- if (!this.isChartJSLoaded) return;
1662
-
1663
- try {
1664
- // Performance Chart with real-time capability
1665
- const performanceCtx = document.getElementById('performanceChartCanvas');
1666
- if (performanceCtx) {
1667
- this.charts.performance = new Chart(performanceCtx, {
1668
- type: 'line',
1669
- data: {
1670
- labels: ['شنبه', 'یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنج‌شنبه', 'جمعه'],
1671
- datasets: [
1672
- {
1673
- label: 'زمان پاسخ (ms)',
1674
- data: this.generateRealisticResponseTimes(),
1675
- borderColor: '#10b981',
1676
- backgroundColor: 'rgba(16, 185, 129, 0.1)',
1677
- tension: 0.4,
1678
- borderWidth: 3,
1679
- pointRadius: 6,
1680
- pointHoverRadius: 8
1681
- },
1682
- {
1683
- label: 'استفاده CPU (%)',
1684
- data: this.generateRealisticCPUUsage(),
1685
- borderColor: '#3b82f6',
1686
- backgroundColor: 'rgba(59, 130, 246, 0.1)',
1687
- tension: 0.4,
1688
- borderWidth: 3,
1689
- pointRadius: 6,
1690
- pointHoverRadius: 8
1691
- }
1692
- ]
1693
- },
1694
- options: {
1695
- responsive: true,
1696
- maintainAspectRatio: false,
1697
- plugins: {
1698
- legend: {
1699
- position: 'top',
1700
- labels: {
1701
- usePointStyle: true,
1702
- padding: 20,
1703
- font: { family: 'Vazirmatn', size: 12 }
1704
- }
1705
- }
1706
- },
1707
- scales: {
1708
- y: {
1709
- beginAtZero: true,
1710
- grid: { color: 'rgba(0, 0, 0, 0.05)' },
1711
- ticks: { font: { family: 'Vazirmatn' } }
1712
- },
1713
- x: {
1714
- grid: { color: 'rgba(0, 0, 0, 0.05)' },
1715
- ticks: { font: { family: 'Vazirmatn' } }
1716
- }
1717
  },
1718
- interaction: { intersect: false, mode: 'index' },
1719
- animation: {
1720
- duration: 750,
1721
- easing: 'easeInOutQuart'
1722
  }
1723
- }
1724
- });
1725
- }
1726
-
1727
- // Status Chart with real data
1728
- const statusCtx = document.getElementById('statusChartCanvas');
1729
- if (statusCtx) {
1730
- this.charts.status = new Chart(statusCtx, {
1731
- type: 'doughnut',
1732
- data: {
1733
- labels: ['API آنلاین', 'OCR آماده', 'PDF فعال', 'Cache فعال'],
1734
- datasets: [{
1735
- data: [1, 1, 1, 1],
1736
- backgroundColor: ['#10b981', '#3b82f6', '#f59e0b', '#ef4444'],
1737
- borderColor: '#ffffff',
1738
- borderWidth: 3,
1739
- hoverBorderWidth: 5
1740
- }]
1741
  },
1742
- options: {
1743
- responsive: true,
1744
- maintainAspectRatio: false,
1745
- plugins: {
1746
- legend: {
1747
- position: 'bottom',
1748
- labels: {
1749
- usePointStyle: true,
1750
- padding: 15,
1751
- font: { family: 'Vazirmatn', size: 11 }
1752
- }
1753
- }
1754
  },
1755
- cutout: '60%',
1756
- animation: {
1757
- animateRotate: true,
1758
- duration: 1000
1759
  }
1760
  }
1761
- });
1762
- }
1763
-
1764
- console.log('📊 Charts initialized with real-time capability');
1765
-
1766
- } catch (error) {
1767
- console.error('❌ Chart initialization failed:', error);
1768
- this.showChartPlaceholders();
1769
- }
1770
- }
1771
-
1772
- showChartPlaceholders() {
1773
- document.querySelectorAll('.chart-placeholder').forEach(placeholder => {
1774
- placeholder.style.display = 'flex';
1775
- placeholder.innerHTML = `
1776
- <i class="fas fa-exclamation-triangle" style="color: #f59e0b;"></i>
1777
- <p>Chart.js بارگذاری نشد</p>
1778
- <small>نمودارها در دسترس نیستند</small>
1779
- `;
1780
- });
1781
-
1782
- document.querySelectorAll('canvas[id$="Canvas"]').forEach(canvas => {
1783
- canvas.style.display = 'none';
1784
- });
1785
- }
1786
-
1787
- setupEventListeners() {
1788
- // Mobile menu toggle
1789
- const mobileMenuToggle = document.getElementById('mobileMenuToggle');
1790
- const sidebar = document.getElementById('sidebar');
1791
-
1792
- if (mobileMenuToggle && sidebar) {
1793
- mobileMenuToggle.addEventListener('click', () => {
1794
- sidebar.classList.toggle('open');
1795
- });
1796
-
1797
- // Close sidebar when clicking outside on mobile
1798
- document.addEventListener('click', (e) => {
1799
- if (window.innerWidth <= 992 &&
1800
- sidebar.classList.contains('open') &&
1801
- !sidebar.contains(e.target) &&
1802
- !mobileMenuToggle.contains(e.target)) {
1803
- sidebar.classList.remove('open');
1804
- }
1805
- });
1806
- }
1807
-
1808
- // Enhanced search functionality
1809
- const searchInput = document.getElementById('searchInput');
1810
- if (searchInput) {
1811
- let searchTimeout;
1812
- searchInput.addEventListener('input', (e) => {
1813
- clearTimeout(searchTimeout);
1814
- const searchTerm = e.target.value.trim();
1815
-
1816
- if (searchTerm.length > 2) {
1817
- searchTimeout = setTimeout(() => {
1818
- this.performSearch(searchTerm);
1819
- }, 800);
1820
- }
1821
- });
1822
-
1823
- searchInput.addEventListener('keypress', (e) => {
1824
- if (e.key === 'Enter') {
1825
- const searchTerm = e.target.value.trim();
1826
- if (searchTerm.length > 0) {
1827
- this.performSearch(searchTerm);
1828
- }
1829
  }
1830
- });
1831
- }
1832
-
1833
- // Add keyboard shortcuts
1834
- document.addEventListener('keydown', (e) => {
1835
- // Ctrl+K for search
1836
- if (e.ctrlKey && e.key === 'k') {
1837
- e.preventDefault();
1838
- searchInput?.focus();
1839
- }
1840
-
1841
- // F5 for refresh
1842
- if (e.key === 'F5') {
1843
- e.preventDefault();
1844
- this.refreshAllData();
1845
- }
1846
- });
1847
- }
1848
-
1849
- async performSearch(term) {
1850
- try {
1851
- // Redirect to search page with query
1852
- window.location.href = `search.html?q=${encodeURIComponent(term)}`;
1853
- } catch (error) {
1854
- console.error('Search failed:', error);
1855
- if (window.notificationManager) {
1856
- window.notificationManager.showError('خطا در جستجو', 'جستجو امکان‌پذیر نیست');
1857
- }
1858
- }
1859
- }
1860
-
1861
- async refreshAllData() {
1862
- try {
1863
- if (window.notificationManager) {
1864
- window.notificationManager.showInfo('بروزرسانی', 'در حال بروزرسانی داده‌ها...');
1865
- }
1866
-
1867
- await this.loadDashboardData();
1868
- this.updateChartsWithRealData();
1869
-
1870
- if (window.notificationManager) {
1871
- window.notificationManager.showSuccess('بروزرسانی موفق', 'داده‌ها به‌روزرسانی شدند');
1872
- }
1873
- } catch (error) {
1874
- console.error('Refresh failed:', error);
1875
- if (window.notificationManager) {
1876
- window.notificationManager.showError('خطا در بروزرسانی', 'نتوانستیم داده‌ها را بروزرسانی کنیم');
1877
- }
1878
- }
1879
- }
1880
-
1881
- // Cleanup method
1882
- destroy() {
1883
- if (this.updateInterval) {
1884
- clearInterval(this.updateInterval);
1885
- }
1886
-
1887
- Object.values(this.charts).forEach(chart => {
1888
- if (chart && typeof chart.destroy === 'function') {
1889
- chart.destroy();
1890
  }
1891
  });
1892
  }
1893
- }
1894
 
1895
- // Chart update functions (global scope for onclick handlers)
1896
- function updatePerformanceChart(period) {
1897
- if (!window.dashboard || !window.dashboard.isChartJSLoaded || !window.dashboard.charts.performance) {
1898
- if (window.notificationManager) {
1899
- window.notificationManager.showWarning('خطا در نمودار', 'نمودارها در دسترس نیستند');
1900
- }
1901
- return;
1902
- }
1903
-
1904
- // Update active filter
1905
- const button = event.target;
1906
- const chartCard = button.closest('.chart-card');
1907
- chartCard.querySelectorAll('.chart-filter').forEach(btn => btn.classList.remove('active'));
1908
- button.classList.add('active');
1909
-
1910
- const data = {
1911
- daily: {
1912
- labels: ['ساعت 6', 'ساعت 9', 'ساعت 12', 'ساعت 15', 'ساعت 18', 'ساعت 21', 'ساعت 24'],
1913
- responseTime: window.dashboard.generateRealisticResponseTimes(),
1914
- cpuUsage: window.dashboard.generateRealisticCPUUsage()
1915
- },
1916
- weekly: {
1917
- labels: ['شنبه', 'یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنج‌شنبه', 'جمعه'],
1918
- responseTime: window.dashboard.generateRealisticResponseTimes(),
1919
- cpuUsage: window.dashboard.generateRealisticCPUUsage()
1920
- },
1921
- monthly: {
1922
- labels: ['هفته 1', 'هفته 2', 'هفته 3', 'هفته 4'],
1923
- responseTime: [180, 220, 190, 210],
1924
- cpuUsage: [35, 42, 38, 40]
1925
- }
1926
  };
1927
-
1928
- const selectedData = data[period] || data.weekly;
1929
-
1930
- window.dashboard.charts.performance.data.labels = selectedData.labels;
1931
- window.dashboard.charts.performance.data.datasets[0].data = selectedData.responseTime;
1932
- window.dashboard.charts.performance.data.datasets[1].data = selectedData.cpuUsage;
1933
- window.dashboard.charts.performance.update('active');
1934
-
1935
- if (window.notificationManager) {
1936
- const periodText = period === 'daily' ? 'روزانه' : period === 'weekly' ? 'هفتگی' : 'ماهانه';
1937
- window.notificationManager.showInfo('نمودار بروزرسانی شد', `نمودار به حالت ${periodText} تغییر کرد`);
1938
- }
1939
- }
1940
-
1941
- function updateStatusChart(type) {
1942
- if (!window.dashboard || !window.dashboard.isChartJSLoaded || !window.dashboard.charts.status) {
1943
- if (window.notificationManager) {
1944
- window.notificationManager.showWarning('خطا در نمودار', 'نمودارها در دسترس نیستند');
1945
- }
1946
- return;
1947
- }
1948
-
1949
- // Update active filter
1950
- const button = event.target;
1951
- const chartCard = button.closest('.chart-card');
1952
- chartCard.querySelectorAll('.chart-filter').forEach(btn => btn.classList.remove('active'));
1953
- button.classList.add('active');
1954
-
1955
- const data = {
1956
- services: {
1957
- labels: ['API آنلاین', 'OCR آماده', 'PDF فعال', 'Cache فعال'],
1958
- data: [1, 1, 1, 1],
1959
- colors: ['#10b981', '#3b82f6', '#f59e0b', '#ef4444']
1960
- },
1961
- endpoints: {
1962
- labels: ['/health', '/api/ocr/*', '/system/status', '/api/docs'],
1963
- data: [1, 1, 1, 1],
1964
- colors: ['#10b981', '#3b82f6', '#f59e0b', '#8b5cf6']
1965
- }
1966
  };
1967
 
1968
- const selectedData = data[type] || data.services;
1969
-
1970
- window.dashboard.charts.status.data.labels = selectedData.labels;
1971
- window.dashboard.charts.status.data.datasets[0].data = selectedData.data;
1972
- window.dashboard.charts.status.data.datasets[0].backgroundColor = selectedData.colors;
1973
- window.dashboard.charts.status.update('active');
1974
-
1975
- if (window.notificationManager) {
1976
- const typeText = type === 'services' ? 'سرویس‌ها' : 'endpoint ها';
1977
- window.notificationManager.showInfo('نمودار بروزرسانی شد', `نمودار به حالت ${typeText} تغییر کرد`);
1978
- }
1979
- }
1980
-
1981
- // File upload handlers (enhanced with better error handling)
1982
- async function handlePDFUpload(event) {
1983
- const file = event.target.files[0];
1984
- if (!file) return;
1985
-
1986
- if (!file.name.toLowerCase().endsWith('.pdf')) {
1987
- if (window.notificationManager) {
1988
- window.notificationManager.showError('فرمت نامعتبر', 'لطفاً فایل PDF انتخاب کنید');
1989
- }
1990
- return;
1991
- }
1992
-
1993
- if (window.notificationManager) {
1994
- window.notificationManager.showInfo('آپلود شروع شد', 'در حال آپلود فایل PDF...');
1995
- }
1996
-
1997
- try {
1998
- // Use FileUploadHandler if available
1999
- if (window.fileUploadHandler) {
2000
- await window.fileUploadHandler.handlePDFUpload(event);
2001
- } else {
2002
- // Fallback implementation
2003
- const formData = new FormData();
2004
- formData.append('file', file);
2005
-
2006
- const response = await fetch('/api/ocr/extract-pdf', {
2007
- method: 'POST',
2008
- body: formData
2009
- });
2010
-
2011
- if (!response.ok) {
2012
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
2013
- }
2014
-
2015
- const result = await response.json();
2016
-
2017
- if (result.success && result.text) {
2018
- displayResults(result.text, result.method, result.metadata);
2019
- if (window.notificationManager) {
2020
- window.notificationManager.showSuccess('استخراج موفق', 'متن با موفقیت استخراج شد');
2021
- }
2022
-
2023
- // Notify dashboard core
2024
- if (window.dashboardCore) {
2025
- window.dashboardCore.broadcast('documentUploaded', {
2026
- fileName: file.name,
2027
- fileSize: file.size,
2028
- fileType: 'pdf'
2029
- });
2030
- }
2031
- } else {
2032
- throw new Error(result.metadata?.error || 'خطا در پردازش فایل');
2033
- }
2034
- }
2035
-
2036
- } catch (error) {
2037
- console.error('PDF upload error:', error);
2038
- if (window.notificationManager) {
2039
- window.notificationManager.showError('خطا در آپلود', `خطا در پردازش PDF: ${error.message}`);
2040
- }
2041
- } finally {
2042
- // Reset file input
2043
- event.target.value = '';
2044
- }
2045
- }
2046
-
2047
- async function handleImageUpload(event) {
2048
- const file = event.target.files[0];
2049
- if (!file) return;
2050
-
2051
- const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/bmp', 'image/tiff'];
2052
- if (!allowedTypes.includes(file.type)) {
2053
- if (window.notificationManager) {
2054
- window.notificationManager.showError('فرمت نامعتبر', 'لطفاً فایل تصویری معتبر انتخاب کنید');
2055
- }
2056
- return;
2057
- }
2058
 
2059
- if (window.notificationManager) {
2060
- window.notificationManager.showInfo('آپلود شروع شد', 'در حال آپلود تصویر...');
2061
- }
2062
-
2063
- try {
2064
- // Use FileUploadHandler if available
2065
- if (window.fileUploadHandler) {
2066
- await window.fileUploadHandler.handleImageUpload(event);
2067
  } else {
2068
- // Fallback implementation
2069
- const formData = new FormData();
2070
- formData.append('file', file);
2071
-
2072
- const response = await fetch('/api/ocr/extract-image', {
2073
- method: 'POST',
2074
- body: formData
2075
- });
2076
-
2077
- if (!response.ok) {
2078
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
2079
- }
2080
-
2081
- const result = await response.json();
2082
-
2083
- if (result.success && result.text) {
2084
- displayResults(result.text, result.method, result.metadata);
2085
- if (window.notificationManager) {
2086
- window.notificationManager.showSuccess('استخراج موفق', 'متن با موفقیت استخراج شد');
2087
- }
2088
-
2089
- // Notify dashboard core
2090
- if (window.dashboardCore) {
2091
- window.dashboardCore.broadcast('documentUploaded', {
2092
- fileName: file.name,
2093
- fileSize: file.size,
2094
- fileType: 'image'
2095
- });
2096
- }
2097
- } else {
2098
- throw new Error(result.metadata?.error || 'خطا در پردازش تصویر');
2099
- }
2100
- }
2101
-
2102
- } catch (error) {
2103
- console.error('Image upload error:', error);
2104
- if (window.notificationManager) {
2105
- window.notificationManager.showError('خطا در آپلود', `خطا در پردازش تصویر: ${error.message}`);
2106
- }
2107
- } finally {
2108
- // Reset file input
2109
- event.target.value = '';
2110
- }
2111
- }
2112
-
2113
- // Display extraction results (enhanced)
2114
- function displayResults(text, method, metadata) {
2115
- const resultsArea = document.getElementById('resultsArea');
2116
- const extractedText = document.getElementById('extractedText');
2117
-
2118
- if (resultsArea && extractedText) {
2119
- window.dashboard.lastExtractedText = text;
2120
- extractedText.textContent = text;
2121
- resultsArea.style.display = 'block';
2122
-
2123
- // Add enhanced metadata info
2124
- const metadataInfo = document.createElement('div');
2125
- metadataInfo.style.cssText = `
2126
- margin-bottom: 1rem;
2127
- padding: 0.8rem;
2128
- background: rgba(59, 130, 246, 0.1);
2129
- border-radius: 8px;
2130
- font-size: 0.85rem;
2131
- border-left: 4px solid #3b82f6;
2132
- `;
2133
-
2134
- const confidence = metadata?.confidence ? ` | اعتماد: ${Math.round(metadata.confidence * 100)}%` : '';
2135
- const quality = metadata?.quality ? ` | کیفیت: ${Math.round(metadata.quality * 100)}%` : '';
2136
- const processingTime = metadata?.processing_time ? ` | زمان: ${metadata.processing_time}s` : '';
2137
-
2138
- metadataInfo.innerHTML = `
2139
- <div style="font-weight: 600; margin-bottom: 0.5rem;">
2140
- <i class="fas fa-info-circle" style="color: #3b82f6; margin-left: 0.5rem;"></i>
2141
- اطلاعات پردازش
2142
- </div>
2143
- <div>
2144
- <strong>روش:</strong> ${method}${confidence}${quality}${processingTime}
2145
- </div>
2146
- <div style="margin-top: 0.3rem; font-size: 0.8rem; color: #64748b;">
2147
- تعداد کلمات: ${text.split(' ').filter(word => word.length > 0).length} |
2148
- تعداد کاراکتر: ${text.length}
2149
- </div>
2150
- `;
2151
-
2152
- // Remove old metadata if exists
2153
- const oldMetadata = resultsArea.querySelector('.metadata-info');
2154
- if (oldMetadata) oldMetadata.remove();
2155
-
2156
- metadataInfo.className = 'metadata-info';
2157
- resultsArea.insertBefore(metadataInfo, extractedText);
2158
-
2159
- // Smooth scroll to results
2160
- resultsArea.scrollIntoView({ behavior: 'smooth', block: 'start' });
2161
- }
2162
- }
2163
-
2164
- // Utility functions (enhanced)
2165
- function copyToClipboard() {
2166
- const extractedText = document.getElementById('extractedText');
2167
- if (extractedText && extractedText.textContent) {
2168
- navigator.clipboard.writeText(extractedText.textContent).then(() => {
2169
- if (window.notificationManager) {
2170
- window.notificationManager.showSuccess('کپی شد', 'متن در کلیپ‌بورد کپی شد');
2171
- }
2172
- }).catch(() => {
2173
- if (window.notificationManager) {
2174
- window.notificationManager.showError('خطا در کپی', 'متن کپی نشد');
2175
- }
2176
- });
2177
- } else {
2178
- if (window.notificationManager) {
2179
- window.notificationManager.showWarning('محتوا یافت نشد', 'متنی برای کپی کردن موجود نیست');
2180
  }
 
2181
  }
2182
- }
2183
 
2184
- function clearResults() {
2185
- const resultsArea = document.getElementById('resultsArea');
2186
- const extractedText = document.getElementById('extractedText');
2187
-
2188
- if (resultsArea && extractedText) {
2189
- resultsArea.style.display = 'none';
2190
- extractedText.textContent = '';
2191
- if (window.dashboard) {
2192
- window.dashboard.lastExtractedText = '';
2193
- }
2194
-
2195
- // Remove metadata
2196
- const metadata = resultsArea.querySelector('.metadata-info');
2197
- if (metadata) metadata.remove();
2198
-
2199
- if (window.notificationManager) {
2200
- window.notificationManager.showInfo('پاک شد', 'نتایج پاک شدند');
2201
- }
2202
- }
2203
- }
2204
-
2205
- // Initialize Enhanced Dashboard
2206
- window.dashboard = new EnhancedDashboardController();
2207
-
2208
- // Setup page visibility change handler for performance
2209
- document.addEventListener('visibilitychange', () => {
2210
- if (document.hidden) {
2211
- // Page is hidden, pause updates
2212
- if (window.dashboard.updateInterval) {
2213
- clearInterval(window.dashboard.updateInterval);
2214
- }
2215
- } else {
2216
- // Page is visible, resume updates
2217
- window.dashboard.startRealTimeUpdates();
2218
- }
2219
- });
2220
-
2221
- // Setup beforeunload handler for cleanup
2222
- window.addEventListener('beforeunload', () => {
2223
- if (window.dashboard) {
2224
- window.dashboard.destroy();
2225
- }
2226
  });
2227
-
2228
- console.log('🏠 Enhanced Legal Dashboard Ready!');
2229
- console.log('📊 Features: Real-time Updates, Multi-page Navigation, Smart OCR Upload');
2230
- console.log('🎯 Status: Fully Functional with API Integration');
2231
- console.log('🔗 Connected Scripts: notifications.js, api-client.js, core.js, file-upload-handler.js, document-crud.js, scraping-control.js');
2232
  </script>
2233
  </body>
2234
  </html>
 
 
1
  <html lang="fa" dir="rtl">
2
  <head>
3
  <meta charset="UTF-8">
 
770
  100% { transform: rotate(360deg); }
771
  }
772
 
773
+ /* بخش فعالیت‌های اخیر */
774
+ .recent-activity-section {
775
+ margin-bottom: 1rem;
776
+ }
777
+
778
+ .activity-list {
779
+ list-style: none;
780
+ padding: 0;
781
+ margin: 0;
782
+ }
783
+
784
+ .activity-item {
785
+ display: flex;
786
+ align-items: center;
787
+ gap: 1rem;
788
+ padding: 0.8rem 1rem;
789
+ background: rgba(255, 255, 255, 0.7);
790
+ border-radius: var(--border-radius-sm);
791
+ margin-bottom: 0.5rem;
792
+ border-right: 3px solid;
793
+ transition: var(--transition-smooth);
794
+ position: relative;
795
+ cursor: pointer;
796
+ }
797
+
798
+ .activity-item:hover {
799
+ transform: translateY(-2px);
800
+ box-shadow: var(--shadow-sm);
801
+ }
802
+
803
+ .activity-item.document { border-right-color: #3b82f6; }
804
+ .activity-item.upload { border-right-color: #10b981; }
805
+ .activity-item.search { border-right-color: #f59e0b; }
806
+
807
+ .activity-icon {
808
+ width: 2.2rem;
809
+ height: 2.2rem;
810
+ min-width: 2.2rem;
811
+ border-radius: 50%;
812
+ display: flex;
813
+ align-items: center;
814
+ justify-content: center;
815
+ color: white;
816
+ font-size: 1rem;
817
+ }
818
+
819
+ .activity-item.document .activity-icon { background: var(--primary-gradient); }
820
+ .activity-item.upload .activity-icon { background: var(--success-gradient); }
821
+ .activity-item.search .activity-icon { background: var(--warning-gradient); }
822
+
823
+ .activity-content {
824
+ flex: 1;
825
+ }
826
+
827
+ .activity-title {
828
+ font-size: var(--font-size-sm);
829
+ font-weight: 600;
830
+ margin-bottom: 0.2rem;
831
+ }
832
+
833
+ .activity-meta {
834
+ font-size: var(--font-size-xs);
835
+ color: var(--text-secondary);
836
+ }
837
+
838
+
839
  /* واکنش‌گرایی */
840
  @media (max-width: 992px) {
841
  .mobile-menu-toggle {
 
915
  </head>
916
  <body>
917
  <div class="dashboard-container">
 
918
  <aside class="sidebar" id="sidebar">
919
  <div class="sidebar-header">
920
  <div class="logo">
 
947
  <span>آنالیتیکس پیشرفته</span>
948
  </a>
949
  </li>
950
+ <li class="nav-item">
951
+ <a href="improved_legal_dashboard.html" class="nav-link">
952
+ <i class="fas fa-gavel nav-icon"></i>
953
+ <span>داشبورد حقوقی</span>
954
+ </a>
955
+ </li>
956
  </ul>
957
  </div>
958
 
 
1000
  </div>
1001
 
1002
  <div class="nav-section">
1003
+ <h6 class="nav-title">گزارشات و فعالیت</h6>
1004
  <ul class="nav-menu">
1005
  <li class="nav-item">
1006
  <a href="reports.html" class="nav-link">
 
1008
  <span>گزارشات</span>
1009
  </a>
1010
  </li>
1011
+ <li class="nav-item">
1012
+ <a href="recent-activity.html" class="nav-link">
1013
+ <i class="fas fa-history nav-icon"></i>
1014
+ <span>فعالیت‌های اخیر</span>
1015
+ </a>
1016
+ </li>
1017
  </ul>
1018
  </div>
1019
 
1020
  <div class="nav-section">
1021
+ <h6 class="nav-title">سیستم</h6>
1022
  <ul class="nav-menu">
1023
  <li class="nav-item">
1024
  <a href="settings.html" class="nav-link">
 
1040
  </li>
1041
  </ul>
1042
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1043
  </nav>
1044
  </aside>
1045
 
 
1046
  <main class="main-content" id="mainContent">
 
1047
  <header class="dashboard-header">
1048
  <div>
1049
  <h1 class="dashboard-title">
 
1051
  <span>داشبورد مدیریتی حقوقی</span>
1052
  </h1>
1053
  </div>
 
1054
  <div class="header-actions">
1055
  <button type="button" class="mobile-menu-toggle" id="mobileMenuToggle" aria-label="منوی موبایل">
1056
  <i class="fas fa-bars"></i>
1057
  </button>
 
1058
  <div class="search-container">
1059
  <input type="text" class="search-input" id="searchInput" placeholder="جستجو در سیستم...">
1060
  <i class="fas fa-search search-icon"></i>
1061
  </div>
 
1062
  <div class="user-profile">
1063
  <div class="user-avatar">ح</div>
1064
  <div class="user-info">
 
1070
  </div>
1071
  </header>
1072
 
 
1073
  <section aria-labelledby="stats-section">
1074
  <h2 id="stats-section" class="sr-only">آمار و ارقام کلیدی</h2>
1075
  <div class="stats-grid">
 
1091
  </div>
1092
  </div>
1093
  </div>
 
1094
  <div class="stat-card success">
1095
  <div class="stat-header">
1096
  <div class="stat-content">
 
1109
  </div>
1110
  </div>
1111
  </div>
 
1112
  <div class="stat-card warning">
1113
  <div class="stat-header">
1114
  <div class="stat-content">
 
1127
  </div>
1128
  </div>
1129
  </div>
 
1130
  <div class="stat-card danger">
1131
  <div class="stat-header">
1132
  <div class="stat-content">
 
1148
  </div>
1149
  </section>
1150
 
 
1151
  <section class="charts-section">
1152
  <div class="chart-card">
1153
  <div class="chart-header">
 
1166
  </div>
1167
  </div>
1168
  </div>
 
1169
  <div class="chart-card">
1170
  <div class="chart-header">
1171
+ <h2 class="chart-title">فعالیت‌های اخیر</h2>
1172
+ <a href="recent-activity.html" class="chart-filter">مشاهده همه</a>
 
 
 
 
 
 
 
 
 
 
1173
  </div>
1174
+ <ul class="activity-list" id="activityList">
1175
+ </ul>
1176
  </div>
1177
  </section>
1178
 
 
1179
  <section class="quick-access-section">
1180
+ <h2 class="sr-only">دسترسی سریع</h2>
1181
+ <div class="quick-access-grid">
1182
+ <a href="upload.html" class="quick-access-item">
1183
+ <div class="quick-access-icon">
1184
+ <i class="fas fa-file-import"></i>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1185
  </div>
1186
+ <div class="quick-access-content">
1187
+ <h3>آپلود اسناد جدید</h3>
1188
+ <p>فایل‌های PDF و تصویری را برای پردازش اضافه کنید.</p>
 
 
 
 
 
 
 
 
 
 
1189
  </div>
1190
+ </a>
1191
+ <a href="search.html" class="quick-access-item">
1192
+ <div class="quick-access-icon">
1193
+ <i class="fas fa-search-plus"></i>
 
 
 
 
1194
  </div>
1195
+ <div class="quick-access-content">
1196
+ <h3>جستجوی هوشمند</h3>
1197
+ <p>متن‌های استخراج شده را با سرعت بالا جستجو کنید.</p>
 
 
 
 
1198
  </div>
1199
+ </a>
1200
+ <a href="reports.html" class="quick-access-item">
1201
+ <div class="quick-access-icon">
1202
+ <i class="fas fa-chart-bar"></i>
1203
+ </div>
1204
+ <div class="quick-access-content">
1205
+ <h3>گزارشات تحلیلی</h3>
1206
+ <p>عملکرد سیستم و آمار اسناد را مشاهده کنید.</p>
1207
+ </div>
1208
+ </a>
1209
+ <a href="settings.html" class="quick-access-item">
1210
+ <div class="quick-access-icon">
1211
+ <i class="fas fa-cog"></i>
1212
+ </div>
1213
+ <div class="quick-access-content">
1214
+ <h3>تنظیمات سیستم</h3>
1215
+ <p>تنظیمات حساب کاربری و سیستم را مدیریت کنید.</p>
1216
+ </div>
1217
+ </a>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1218
  </div>
1219
  </section>
1220
  </main>
1221
  </div>
1222
 
 
1223
  <div class="toast-container" id="toastContainer"></div>
1224
 
1225
+ <div class="connection-status" id="connectionStatus">
1226
+ <span class="status-indicator"></span>
1227
+ <span id="connectionText">...</span>
 
1228
  </div>
1229
 
 
 
 
 
 
 
 
 
1230
  <script>
1231
+ // اسکریپت‌های داشبورد
1232
+ document.addEventListener('DOMContentLoaded', () => {
1233
+ const sidebar = document.getElementById('sidebar');
1234
+ const mobileMenuToggle = document.getElementById('mobileMenuToggle');
1235
+ const connectionStatus = document.getElementById('connectionStatus');
1236
+ const connectionText = document.getElementById('connectionText');
1237
+ const toastContainer = document.getElementById('toastContainer');
1238
+ const activityList = document.getElementById('activityList');
1239
+
1240
+ let isSidebarOpen = false;
1241
+ mobileMenuToggle.addEventListener('click', () => {
1242
+ isSidebarOpen = !isSidebarOpen;
1243
+ sidebar.classList.toggle('open');
1244
+ });
1245
+
1246
+ // Handle outside click to close sidebar on mobile
1247
+ document.addEventListener('click', (event) => {
1248
+ const target = event.target;
1249
+ if (!sidebar.contains(target) && !mobileMenuToggle.contains(target) && isSidebarOpen) {
1250
+ sidebar.classList.remove('open');
1251
+ isSidebarOpen = false;
 
 
 
 
1252
  }
1253
+ });
1254
+
1255
+ // عملکرد واقعی‌نما برای Toast Notification
1256
+ function showToast(type, title, message) {
1257
+ const toast = document.createElement('div');
1258
+ toast.className = `toast ${type}`;
1259
+ toast.innerHTML = `
1260
+ <i class="toast-icon fas fa-${type === 'success' ? 'check-circle' : type === 'error' ? 'times-circle' : 'info-circle'}"></i>
1261
+ <div class="toast-content">
1262
+ <h6 class="toast-title">${title}</h6>
1263
+ <p class="toast-message">${message}</p>
1264
+ </div>
1265
+ <button type="button" class="toast-close"><i class="fas fa-times"></i></button>
1266
+ `;
1267
+ toastContainer.appendChild(toast);
1268
+ setTimeout(() => toast.classList.add('show'), 10);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1269
 
1270
+ const closeButton = toast.querySelector('.toast-close');
1271
+ closeButton.addEventListener('click', () => {
1272
+ toast.classList.remove('show');
1273
+ setTimeout(() => toast.remove(), 300);
1274
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1275
 
1276
+ setTimeout(() => {
1277
+ toast.classList.remove('show');
1278
+ setTimeout(() => toast.remove(), 300);
1279
+ }, 5000);
 
1280
  }
1281
 
1282
+ // چک کردن وضعیت اتصال به سرور (ریل تایم شبیه‌سازی شده)
1283
+ function checkConnectionStatus() {
1284
+ const isOnline = Math.random() > 0.1; // 90% online
1285
+ if (isOnline) {
1286
+ connectionStatus.classList.remove('offline');
1287
+ connectionStatus.classList.add('online');
1288
+ connectionText.textContent = 'اتصال برقرار است';
1289
+ } else {
1290
+ connectionStatus.classList.remove('online');
1291
+ connectionStatus.classList.add('offline');
1292
+ connectionText.textContent = 'قطع اتصال';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1293
  }
1294
  }
1295
 
1296
+ // اضافه کردن فعالیت‌های اخیر (ریل تایم شبیه‌سازی شده)
1297
+ const recentActivities = [
1298
+ { type: 'document', title: 'بارگذاری سند "قرارداد فروش"', time: '1 دقیقه پیش', icon: 'file-signature' },
1299
+ { type: 'upload', title: 'آپلود فایل "صورت‌جلسه.pdf"', time: '30 دقیقه پیش', icon: 'cloud-upload-alt' },
1300
+ { type: 'search', title: 'جستجو برای "ماده ۲۳ قانون تجارت"', time: '1 ساعت پیش', icon: 'search' },
1301
+ { type: 'upload', title: 'آپلود فایل "وکالت‌نامه.jpg"', time: '2 ساعت پیش', icon: 'image' },
1302
+ ];
1303
+
1304
+ function renderRecentActivities() {
1305
+ activityList.innerHTML = '';
1306
+ recentActivities.forEach(activity => {
1307
+ const activityItem = document.createElement('li');
1308
+ activityItem.className = `activity-item ${activity.type}`;
1309
+ activityItem.innerHTML = `
1310
+ <div class="activity-icon">
1311
+ <i class="fas fa-${activity.icon}"></i>
1312
+ </div>
1313
+ <div class="activity-content">
1314
+ <div class="activity-title">${activity.title}</div>
1315
+ <div class="activity-meta"><span>${activity.time}</span></div>
1316
+ </div>
1317
+ `;
1318
+ activityList.appendChild(activityItem);
1319
+ });
 
1320
  }
1321
 
1322
+ // بروزرسانی وضعیت سیستم و نمودار (ریل تایم شبیه‌سازی شده)
1323
+ const apiStatusElem = document.getElementById('apiStatus');
1324
+ const ocrStatusElem = document.getElementById('ocrStatus');
1325
+ const documentsCountElem = document.getElementById('documentsCount');
1326
+ const responseTimeElem = document.getElementById('responseTime');
1327
+
1328
+ function updateSystemStatus() {
1329
+ apiStatusElem.innerHTML = '<i class="fas fa-check-circle" style="color: #10b981;"></i> سالم';
1330
+ ocrStatusElem.innerHTML = '<i class="fas fa-check-circle" style="color: #10b981;"></i> آماده';
1331
+ documentsCountElem.textContent = Math.floor(Math.random() * 500) + 100; // بین ۱۰۰ تا ۶۰۰
1332
+ responseTimeElem.textContent = Math.floor(Math.random() * 100) + 50; // بین ۵۰ تا ۱۵۰ میلی‌ثانیه
 
 
 
 
 
 
 
 
1333
  }
1334
 
1335
+ // نمودار عملکرد (نمونه)
1336
+ const performanceChartCanvas = document.getElementById('performanceChartCanvas').getContext('2d');
1337
+ let performanceChart;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1338
 
1339
+ function createPerformanceChart(data) {
1340
+ if (performanceChart) {
1341
+ performanceChart.destroy();
 
 
 
 
 
1342
  }
1343
+ performanceChart = new Chart(performanceChartCanvas, {
1344
+ type: 'line',
1345
+ data: {
1346
+ labels: data.labels,
1347
+ datasets: [{
1348
+ label: 'پردازش‌های موفق',
1349
+ data: data.values,
1350
+ backgroundColor: 'rgba(59, 130, 246, 0.2)',
1351
+ borderColor: 'rgba(59, 130, 246, 1)',
1352
+ borderWidth: 2,
1353
+ fill: true,
1354
+ tension: 0.4
1355
+ }]
1356
+ },
1357
+ options: {
1358
+ responsive: true,
1359
+ maintainAspectRatio: false,
1360
+ plugins: {
1361
+ legend: {
1362
+ display: false
1363
+ }
1364
+ },
1365
+ scales: {
1366
+ x: {
1367
+ grid: {
1368
+ display: false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1369
  },
1370
+ ticks: {
1371
+ color: 'var(--text-secondary)'
 
 
1372
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1373
  },
1374
+ y: {
1375
+ grid: {
1376
+ color: 'rgba(0, 0, 0, 0.05)'
 
 
 
 
 
 
 
 
 
1377
  },
1378
+ ticks: {
1379
+ color: 'var(--text-secondary)'
 
 
1380
  }
1381
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1382
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1383
  }
1384
  });
1385
  }
 
1386
 
1387
+ const weeklyData = {
1388
+ labels: ['شنبه', 'یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنجشنبه', 'جمعه'],
1389
+ values: [65, 59, 80, 81, 56, 55, 40]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1390
  };
1391
+ const monthlyData = {
1392
+ labels: ['هفته اول', 'هفته دوم', 'هفته سوم', 'هفته چهارم'],
1393
+ values: [220, 190, 310, 280]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1394
  };
1395
 
1396
+ function updatePerformanceChart(period) {
1397
+ const filters = document.querySelectorAll('.chart-filter');
1398
+ filters.forEach(filter => filter.classList.remove('active'));
1399
+ event.target.classList.add('active');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1400
 
1401
+ let data;
1402
+ if (period === 'weekly') {
1403
+ data = weeklyData;
1404
+ } else if (period === 'monthly') {
1405
+ data = monthlyData;
 
 
 
1406
  } else {
1407
+ data = {
1408
+ labels: ['ساعت 1', 'ساعت 2', 'ساعت 3', 'ساعت 4', 'ساعت 5', 'ساعت 6'],
1409
+ values: [12, 19, 3, 5, 2, 3]
1410
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1411
  }
1412
+ createPerformanceChart(data);
1413
  }
 
1414
 
1415
+ // فراخوانی اولیه و تکرار
1416
+ checkConnectionStatus();
1417
+ updateSystemStatus();
1418
+ renderRecentActivities();
1419
+ updatePerformanceChart('weekly'); // بارگذاری اولیه نمودار هفتگی
1420
+
1421
+ setInterval(checkConnectionStatus, 15000); // هر ۱۵ ثانیه
1422
+ setInterval(updateSystemStatus, 30000); // هر ۳۰ ثانیه
1423
+
1424
+ // شبیه‌سازی نوتیفیکیشن‌ها برای نشان دادن عملکرد
1425
+ setTimeout(() => {
1426
+ showToast('success', 'بارگذاری موفق', 'فایل "قرارداد-ملکی.pdf" با موفقیت پردازش شد.');
1427
+ }, 3000);
1428
+ setTimeout(() => {
1429
+ showToast('warning', 'هشدار', 'برخی اسناد نیاز به بررسی دستی دارند.');
1430
+ }, 8000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1431
  });
 
 
 
 
 
1432
  </script>
1433
  </body>
1434
  </html>