Hoghoghi / frontend /improved_legal_dashboard.html
Really-amin's picture
Upload 46 files
922c3ba verified
raw
history blame
68.5 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>
<!-- Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap&subset=latin" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/gh/rastikerdar/[email protected]/Vazirmatn-font-face.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
/* Professional Color Palette */
--bg-primary: #0a0a0a;
--bg-secondary: #1a1a1a;
--bg-tertiary: #2a2a2a;
--surface: #ffffff;
--surface-variant: #f8f9fa;
/* Text Colors */
--text-primary: #000000;
--text-secondary: #4a5568;
--text-muted: #a0aec0;
--text-inverse: #ffffff;
/* Metallic Gradients */
--gold-gradient: linear-gradient(135deg, #ffd700 0%, #ffed4e 50%, #ffd700 100%);
--silver-gradient: linear-gradient(135deg, #c0c0c0 0%, #e8e8e8 50%, #c0c0c0 100%);
--platinum-gradient: linear-gradient(135deg, #e5e4e2 0%, #f7f7f7 50%, #e5e4e2 100%);
--bronze-gradient: linear-gradient(135deg, #cd7f32 0%, #daa520 50%, #cd7f32 100%);
/* Accent Colors */
--accent-primary: #3b82f6;
--accent-secondary: #10b981;
--accent-tertiary: #f59e0b;
--accent-error: #ef4444;
/* Status Colors */
--success: #10b981;
--warning: #f59e0b;
--error: #ef4444;
--info: #3b82f6;
/* Shadows */
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 25px 50px rgba(0, 0, 0, 0.15);
--shadow-layered: 0 5px 15px rgba(0,0,0,0.08);
/* Border Radius */
--radius-sm: 6px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
/* Transitions */
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
--transition-smooth: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
--transition-elegant: all 0.4s cubic-bezier(0.165, 0.84, 0.44, 1);
/* Layout */
--sidebar-width: 300px;
--sidebar-collapsed: 80px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Vazirmatn', 'Inter', sans-serif;
background: linear-gradient(135deg, var(--bg-primary) 0%, #111 100%);
color: var(--text-inverse);
line-height: 1.6;
font-size: 15px;
font-weight: 400;
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* Loading Screen */
.loading-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--bg-primary);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 9999;
transition: opacity 0.3s ease;
}
.loading-screen.hidden {
opacity: 0;
pointer-events: none;
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid transparent;
border-top: 3px solid var(--surface);
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
color: var(--text-inverse);
font-size: 16px;
font-weight: 500;
}
/* Main Layout */
.dashboard {
display: flex;
min-height: 100vh;
opacity: 0;
transition: opacity 0.3s ease;
}
.dashboard.loaded {
opacity: 1;
}
/* Mobile Menu Button */
.mobile-menu-btn {
display: none;
position: fixed;
top: 15px;
left: 15px;
z-index: 1100;
width: 44px;
height: 44px;
background: var(--gold-gradient);
border: none;
border-radius: var(--radius-md);
cursor: pointer;
transition: var(--transition-smooth);
color: #000;
font-size: 18px;
}
.mobile-menu-btn:hover {
transform: scale(1.05);
box-shadow: var(--shadow-md);
}
/* Sidebar Overlay for Mobile */
.sidebar-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.sidebar-overlay.active {
opacity: 1;
visibility: visible;
}
/* Enhanced Sidebar */
.sidebar {
width: var(--sidebar-width);
background: var(--bg-secondary);
border-left: 1px solid rgba(255,255,255,0.05);
position: fixed;
height: 100vh;
right: 0;
top: 0;
overflow-y: auto;
transition: var(--transition);
z-index: 1000;
box-shadow: -5px 0 15px rgba(0,0,0,0.2);
display: flex;
flex-direction: column;
}
.sidebar.collapsed {
width: var(--sidebar-collapsed);
}
.sidebar-header {
padding: 1.5rem;
border-bottom: 1px solid rgba(255,255,255,0.1);
position: relative;
text-align: center;
}
.sidebar.collapsed .sidebar-header {
padding: 1.5rem 0.5rem;
}
.logo {
font-size: 22px;
font-weight: 700;
color: var(--text-inverse);
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
}
.logo-icon {
background: var(--gold-gradient);
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #000;
font-size: 18px;
}
.logo-text {
transition: var(--transition);
}
.sidebar.collapsed .logo-text {
display: none;
}
.subtitle {
font-size: 13px;
color: #aaa;
margin-top: 0.5rem;
transition: var(--transition);
}
.sidebar.collapsed .subtitle {
display: none;
}
.toggle-btn {
position: absolute;
left: -12px;
top: 50%;
transform: translateY(-50%);
width: 28px;
height: 28px;
background: var(--bg-secondary);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: var(--text-inverse);
transition: var(--transition);
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.toggle-btn:hover {
background: var(--bg-tertiary);
transform: translateY(-50%) scale(1.1);
border-color: rgba(255,215,0,0.3);
}
/* Navigation */
.nav {
padding: 1.5rem 0;
flex-grow: 1;
}
.nav-group {
margin-bottom: 1.5rem;
}
.nav-group-title {
padding: 0.5rem 1.5rem;
font-size: 12px;
color: #777;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 0.5rem;
}
.sidebar.collapsed .nav-group-title {
display: none;
}
.nav-item {
position: relative;
margin-bottom: 0.25rem;
}
.nav-link {
display: flex;
align-items: center;
padding: 1rem 1.5rem;
color: #ccc;
text-decoration: none;
transition: var(--transition);
cursor: pointer;
font-weight: 500;
font-size: 15px;
position: relative;
overflow: hidden;
border-radius: var(--radius-sm);
margin: 0 0.5rem;
}
.nav-link:hover {
background: rgba(255,255,255,0.05);
color: #fff;
}
.nav-link.active {
background: linear-gradient(90deg, rgba(59, 130, 246, 0.2), transparent);
color: var(--accent-primary);
font-weight: 600;
}
.nav-link.active::after {
content: '';
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: 3px;
background: var(--accent-primary);
}
.nav-icon {
width: 24px;
height: 24px;
margin-left: 1rem;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition);
font-size: 18px;
color: #aaa;
}
.nav-link.active .nav-icon,
.nav-link:hover .nav-icon {
color: var(--accent-primary);
}
.nav-text {
transition: var(--transition);
font-weight: 500;
}
.sidebar.collapsed .nav-text {
display: none;
}
.sidebar.collapsed .nav-link {
justify-content: center;
padding: 1.1rem 0.5rem;
margin: 0.25rem;
border-radius: var(--radius-md);
}
.sidebar.collapsed .nav-icon {
margin: 0;
font-size: 20px;
}
/* User Section */
.sidebar-footer {
padding: 1.5rem;
border-top: 1px solid rgba(255,255,255,0.1);
display: flex;
align-items: center;
gap: 1rem;
}
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--gold-gradient);
display: flex;
align-items: center;
justify-content: center;
color: #000;
font-weight: bold;
flex-shrink: 0;
font-size: 16px;
}
.user-info {
flex-grow: 1;
}
.user-name {
font-weight: 600;
color: #fff;
font-size: 14px;
}
.user-role {
font-size: 12px;
color: #aaa;
}
.logout-btn {
background: none;
border: none;
color: #999;
font-size: 18px;
cursor: pointer;
transition: var(--transition);
padding: 0.5rem;
border-radius: var(--radius-sm);
}
.logout-btn:hover {
color: var(--accent-error);
background: rgba(239, 68, 68, 0.1);
}
.sidebar.collapsed .user-info,
.sidebar.collapsed .logout-btn {
display: none;
}
.sidebar.collapsed .user-avatar {
margin: 0 auto;
}
/* Main Content */
.main-content {
flex: 1;
margin-right: var(--sidebar-width);
background: linear-gradient(to bottom, #f9fafb, #ffffff);
min-height: 100vh;
transition: var(--transition);
}
.main-content.collapsed {
margin-right: var(--sidebar-collapsed);
}
/* Header */
.header {
background: var(--surface);
padding: 1.5rem 2rem;
border-bottom: 1px solid #e2e8f0;
display: flex;
align-items: center;
justify-content: space-between;
position: sticky;
top: 0;
z-index: 100;
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
}
.header-title {
font-size: 24px;
font-weight: 700;
color: var(--text-primary);
background: var(--gold-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.header-actions {
display: flex;
align-items: center;
gap: 1rem;
}
.search-box {
position: relative;
}
.search-input {
width: 300px;
padding: 0.75rem 1rem 0.75rem 2.5rem;
border: 1px solid #d1d5db;
border-radius: var(--radius-lg);
background: var(--surface-variant);
color: var(--text-primary);
font-size: 14px;
transition: var(--transition);
}
.search-input:focus {
outline: none;
border-color: var(--accent-primary);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
.search-icon {
position: absolute;
right: 0.75rem;
top: 50%;
transform: translateY(-50%);
color: var(--text-muted);
}
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: var(--radius-md);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: var(--transition-smooth);
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-primary {
background: var(--accent-primary);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(59, 130, 246, 0.3);
}
/* Content Area */
.content {
padding: 2rem;
}
/* Enhanced Stats Grid */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.stat-card {
background: var(--surface);
padding: 1.5rem;
border-radius: var(--radius-xl);
border: 1px solid #e2e8f0;
box-shadow: var(--shadow-layered);
transition: var(--transition-elegant);
position: relative;
overflow: hidden;
}
.stat-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: var(--gold-gradient);
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: 0 20px 30px -10px rgba(0,0,0,0.2);
}
.stat-card.gold::before { background: var(--gold-gradient); }
.stat-card.silver::before { background: var(--silver-gradient); }
.stat-card.bronze::before { background: var(--bronze-gradient); }
.stat-card.platinum::before { background: var(--platinum-gradient); }
.stat-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
}
.stat-title {
font-size: 14px;
color: var(--text-muted);
font-weight: 500;
}
.stat-icon {
width: 40px;
height: 40px;
border-radius: var(--radius-md);
display: flex;
align-items: center;
justify-content: center;
color: var(--text-inverse);
font-size: 18px;
}
.stat-card.gold .stat-icon { background: var(--gold-gradient); color: #000; }
.stat-card.silver .stat-icon { background: var(--silver-gradient); color: var(--text-primary); }
.stat-card.bronze .stat-icon { background: var(--bronze-gradient); }
.stat-card.platinum .stat-icon { background: var(--platinum-gradient); color: var(--text-primary); }
.stat-value {
font-size: 28px;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 0.5rem;
}
.stat-change {
font-size: 12px;
display: flex;
align-items: center;
gap: 0.25rem;
}
.stat-change.positive { color: var(--success); }
.stat-change.negative { color: var(--error); }
/* Charts Section */
.charts-grid {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 2rem;
margin-bottom: 2rem;
}
.chart-card {
background: var(--surface);
padding: 1.5rem;
border-radius: var(--radius-xl);
border: 1px solid #e2e8f0;
box-shadow: var(--shadow-layered);
direction: rtl;
text-align: right;
}
.chart-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1.5rem;
}
.chart-title {
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 0.5rem;
}
.chart-container {
position: relative;
height: 300px;
direction: rtl;
}
/* Table */
.table-card {
background: var(--surface);
border-radius: var(--radius-xl);
border: 1px solid #e2e8f0;
box-shadow: var(--shadow-layered);
overflow: hidden;
direction: rtl;
text-align: right;
}
.table-header {
padding: 1.5rem;
border-bottom: 1px solid #e2e8f0;
display: flex;
align-items: center;
justify-content: space-between;
}
.table-title {
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 0.5rem;
}
.table {
width: 100%;
border-collapse: collapse;
}
.table th {
padding: 1rem 1.5rem;
text-align: right;
font-weight: 600;
color: var(--text-secondary);
background: var(--surface-variant);
border-bottom: 1px solid #e2e8f0;
font-size: 13px;
}
.table td {
padding: 1rem 1.5rem;
color: var(--text-primary);
border-bottom: 1px solid #f1f5f9;
font-size: 14px;
text-align: right;
}
.table tbody tr:hover {
background: var(--surface-variant);
}
.status-badge {
padding: 0.25rem 0.75rem;
border-radius: var(--radius-sm);
font-size: 12px;
font-weight: 500;
color: var(--text-inverse);
}
.status-badge.published { background: var(--success); }
.status-badge.pending { background: var(--warning); }
.status-badge.error { background: var(--error); }
/* AI Panel */
.ai-panel {
background: var(--surface);
border-radius: var(--radius-xl);
border: 1px solid #e2e8f0;
box-shadow: var(--shadow-layered);
margin-top: 2rem;
overflow: hidden;
}
.ai-panel-header {
padding: 1.5rem;
border-bottom: 1px solid #e2e8f0;
background: linear-gradient(135deg, var(--accent-primary), var(--accent-secondary));
color: white;
}
.ai-panel-title {
font-size: 16px;
font-weight: 600;
display: flex;
align-items: center;
gap: 0.5rem;
}
.ai-suggestions-list {
padding: 1rem;
}
.ai-suggestion-item {
padding: 1rem;
border: 1px solid #e2e8f0;
border-radius: var(--radius-md);
margin-bottom: 1rem;
background: var(--surface-variant);
}
.confidence-badge {
display: inline-block;
padding: 0.25rem 0.5rem;
border-radius: var(--radius-sm);
font-size: 11px;
font-weight: 600;
margin-right: 0.5rem;
}
.confidence-high { background: var(--success); color: white; }
.confidence-medium { background: var(--warning); color: white; }
.confidence-low { background: var(--error); color: white; }
/* No results message */
.no-results {
text-align: center;
padding: 2rem;
color: var(--text-muted);
font-style: italic;
}
/* Chart placeholder for when Chart.js fails */
.chart-placeholder {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: var(--text-muted);
font-style: italic;
background: var(--surface-variant);
border-radius: var(--radius-md);
border: 2px dashed #ddd;
}
/* Modal Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
backdrop-filter: blur(5px);
}
.modal-content {
background: var(--surface);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-xl);
max-width: 600px;
width: 90%;
max-height: 80vh;
overflow: hidden;
direction: rtl;
}
.modal-header {
padding: 1.5rem;
border-bottom: 1px solid #e2e8f0;
display: flex;
align-items: center;
justify-content: space-between;
}
.modal-title {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
}
.modal-close {
background: none;
border: none;
font-size: 20px;
color: var(--text-muted);
cursor: pointer;
padding: 0.5rem;
border-radius: var(--radius-sm);
transition: var(--transition);
}
.modal-close:hover {
color: var(--text-primary);
background: var(--surface-variant);
}
.modal-body {
padding: 1.5rem;
max-height: 60vh;
overflow-y: auto;
}
.modal-footer {
padding: 1.5rem;
border-top: 1px solid #e2e8f0;
display: flex;
gap: 1rem;
justify-content: flex-end;
}
/* Toast Notifications */
.toast-container {
position: fixed;
top: 20px;
left: 20px;
z-index: 3000;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.toast {
background: var(--surface);
border-radius: var(--radius-md);
padding: 1rem 1.5rem;
box-shadow: var(--shadow-lg);
border-left: 4px solid var(--accent-primary);
min-width: 300px;
animation: slideIn 0.3s ease;
}
.toast.success {
border-left-color: var(--success);
}
.toast.error {
border-left-color: var(--error);
}
.toast.warning {
border-left-color: var(--warning);
}
.toast-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.5rem;
}
.toast-title {
font-weight: 600;
color: var(--text-primary);
}
.toast-close {
background: none;
border: none;
color: var(--text-muted);
cursor: pointer;
font-size: 16px;
}
.toast-message {
color: var(--text-secondary);
font-size: 14px;
}
@keyframes slideIn {
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(-100%);
opacity: 0;
}
}
/* No results styling */
.no-results {
text-align: center;
padding: 3rem 2rem;
color: var(--text-muted);
}
.no-results i {
display: block;
margin-bottom: 1rem;
}
/* Confidence badges */
.confidence-badge {
padding: 0.25rem 0.5rem;
border-radius: var(--radius-sm);
font-size: 0.75rem;
font-weight: 500;
}
.confidence-high {
background: var(--success);
color: white;
}
.confidence-medium {
background: var(--warning);
color: white;
}
.confidence-low {
background: var(--error);
color: white;
}
/* AI suggestions panel */
.ai-suggestion-item {
background: var(--surface);
border: 1px solid var(--surface-variant);
border-radius: var(--radius-md);
padding: 1rem;
margin-bottom: 1rem;
}
.ai-suggestion-item:last-child {
margin-bottom: 0;
}
/* Enhanced Mobile Responsive Design */
@media (max-width: 768px) {
.mobile-menu-btn {
display: block;
}
.sidebar {
width: 80%;
transform: translateX(100%);
transition: transform 0.3s ease;
}
.sidebar.open {
transform: translateX(0);
}
.main-content,
.main-content.collapsed {
margin-right: 0;
}
.header {
padding: 1rem;
padding-left: 4rem;
}
.content {
padding: 1rem;
}
.search-input {
width: 200px;
}
.header-title {
font-size: 20px;
}
}
@media (max-width: 480px) {
.search-input {
width: 150px;
}
.header-actions {
flex-direction: column;
gap: 0.5rem;
}
.stat-card {
padding: 1rem;
}
.chart-container {
height: 250px;
}
.modal-content {
width: 95%;
margin: 1rem;
}
.toast {
min-width: 250px;
}
}
/* Additional Polish Styles */
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn:disabled:hover {
transform: none;
box-shadow: none;
}
/* Smooth scrolling */
html {
scroll-behavior: smooth;
}
/* Focus styles for accessibility */
.btn:focus,
.search-input:focus,
.modal-close:focus {
outline: 2px solid var(--accent-primary);
outline-offset: 2px;
}
/* Loading states */
.loading {
opacity: 0.6;
pointer-events: none;
}
.loading::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
margin: -10px 0 0 -10px;
border: 2px solid var(--accent-primary);
border-top: 2px solid transparent;
border-radius: 50%;
animation: spin 1s linear infinite;
}
/* Hover effects for interactive elements */
.nav-link:hover,
.btn:hover,
.stat-card:hover,
.ai-suggestion-item:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
/* Print styles */
@media print {
.sidebar,
.header,
.mobile-menu-btn,
.toast-container,
.modal-overlay {
display: none !important;
}
.main-content {
margin: 0 !important;
}
.content {
padding: 0 !important;
}
}
/* High contrast mode support */
@media (prefers-contrast: high) {
:root {
--text-primary: #000000;
--text-secondary: #333333;
--text-muted: #666666;
--surface: #ffffff;
--surface-variant: #f0f0f0;
}
}
/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
</style>
</head>
<body>
<!-- Loading Screen -->
<div class="loading-screen" id="loadingScreen">
<div class="spinner"></div>
<div class="loading-text">در حال بارگذاری...</div>
</div>
<!-- Mobile Menu Button -->
<button class="mobile-menu-btn" id="mobileMenuBtn" type="button" onclick="toggleMobileSidebar()" aria-label="منوی موبایل">
<i class="fas fa-bars"></i>
</button>
<!-- Sidebar Overlay for Mobile -->
<div class="sidebar-overlay" id="sidebarOverlay" onclick="closeMobileSidebar()"></div>
<!-- Dashboard Container -->
<div class="dashboard" id="dashboard">
<!-- Enhanced Sidebar -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-header">
<div class="toggle-btn" onclick="toggleSidebar()">
<i class="fas fa-chevron-left"></i>
</div>
<div class="logo">
<div class="logo-icon">
<i class="fas fa-balance-scale"></i>
</div>
<div class="logo-text">سیستم حقوقی پیشرفته</div>
</div>
<div class="subtitle">مدیریت هوشمند منابع قضایی</div>
</div>
<nav class="nav">
<div class="nav-group">
<div class="nav-group-title">منوی اصلی</div>
<div class="nav-item">
<a href="#" class="nav-link active">
<div class="nav-icon">
<i class="fas fa-chart-line"></i>
</div>
<span class="nav-text">داشبورد اصلی</span>
</a>
</div>
<div class="nav-item">
<a href="#" class="nav-link">
<div class="nav-icon">
<i class="fas fa-folder"></i>
</div>
<span class="nav-text">دسته‌بندی‌ها</span>
</a>
</div>
<div class="nav-item">
<a href="#" class="nav-link">
<div class="nav-icon">
<i class="fas fa-database"></i>
</div>
<span class="nav-text">منابع داده</span>
</a>
</div>
<div class="nav-item">
<a href="#" class="nav-link">
<div class="nav-icon">
<i class="fas fa-users"></i>
</div>
<span class="nav-text">کاربران سیستم</span>
</a>
</div>
</div>
<div class="nav-group">
<div class="nav-group-title">ابزارها</div>
<div class="nav-item">
<a href="#" class="nav-link">
<div class="nav-icon">
<i class="fas fa-search"></i>
</div>
<span class="nav-text">جستجوی پیشرفته</span>
</a>
</div>
<div class="nav-item">
<a href="#" class="nav-link">
<div class="nav-icon">
<i class="fas fa-chart-pie"></i>
</div>
<span class="nav-text">گزارش‌های تحلیلی</span>
</a>
</div>
<div class="nav-item">
<a href="#" class="nav-link">
<div class="nav-icon">
<i class="fas fa-cog"></i>
</div>
<span class="nav-text">تنظیمات سیستم</span>
</a>
</div>
</div>
</nav>
<div class="sidebar-footer">
<div class="user-avatar">فا</div>
<div class="user-info">
<div class="user-name">فاطمه احمدی</div>
<div class="user-role">مدیر سیستم حقوقی</div>
</div>
<button class="logout-btn" type="button" aria-label="خروج از سیستم">
<i class="fas fa-sign-out-alt"></i>
</button>
</div>
</aside>
<!-- Main Content -->
<main class="main-content" id="mainContent">
<!-- Header -->
<header class="header">
<h1 class="header-title">داشبورد مدیریتی حقوقی</h1>
<div class="header-actions">
<div class="search-box">
<input type="text" class="search-input" id="searchInput" placeholder="جستجو در اسناد حقوقی...">
<i class="fas fa-search search-icon"></i>
</div>
<button class="btn btn-primary" type="button">
<i class="fas fa-plus"></i>
سند جدید
</button>
</div>
</header>
<!-- Content -->
<div class="content">
<!-- Stats Grid -->
<div class="stats-grid" id="stats">
<!-- Dynamic stats cards will be populated by JavaScript -->
</div>
<!-- Charts Grid -->
<div class="charts-grid" id="charts">
<!-- Dynamic charts will be populated by JavaScript -->
</div>
<!-- Documents Table -->
<div class="table-card" id="documents">
<!-- Dynamic documents table will be populated by JavaScript -->
</div>
<!-- AI Suggestions Panel -->
<div class="ai-panel" id="aiSuggestions">
<div class="ai-panel-header">
<div class="ai-panel-title">
<i class="fas fa-brain"></i>
پیشنهادات هوش مصنوعی
</div>
</div>
<div class="ai-suggestions-list" id="aiSuggestionsList">
<!-- AI suggestions will be populated by JavaScript -->
</div>
</div>
<!-- Document Details Modal -->
<div class="modal-overlay" id="documentModal" style="display: none;">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">جزئیات سند</h3>
<button class="modal-close" type="button" onclick="closeDocumentModal()" aria-label="بستن">
<i class="fas fa-times"></i>
</button>
</div>
<div class="modal-body" id="modalBody">
<!-- Document details will be populated by JavaScript -->
</div>
<div class="modal-footer">
<button class="btn btn-primary" type="button" onclick="approveDocument()">
<i class="fas fa-check"></i>
تایید
</button>
<button class="btn" type="button" onclick="rejectDocument()">
<i class="fas fa-times"></i>
رد
</button>
</div>
</div>
</div>
<!-- Toast Notifications -->
<div class="toast-container" id="toastContainer">
<!-- Toast notifications will be added here -->
</div>
</div>
</main>
</div>
<script>
// Basic initialization
document.addEventListener('DOMContentLoaded', function() {
// Show loading screen
setTimeout(() => {
document.getElementById('loadingScreen').classList.add('hidden');
document.getElementById('dashboard').classList.add('loaded');
}, 1500);
});
// Enhanced sidebar functionality
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
const mainContent = document.getElementById('mainContent');
sidebar.classList.toggle('collapsed');
mainContent.classList.toggle('collapsed');
}
// Mobile sidebar functions
function toggleMobileSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
sidebar.classList.add('open');
overlay.classList.add('active');
}
function closeMobileSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebarOverlay');
sidebar.classList.remove('open');
overlay.classList.remove('active');
}
// Global variables for data management
let currentData = {
documents: [],
stats: {},
charts: {},
aiSuggestions: []
};
let currentPage = 1;
const itemsPerPage = 10;
let websocket = null;
// API endpoints - Updated to work with your FastAPI backend
const API_ENDPOINTS = {
dashboardSummary: 'http://localhost:8000/api/dashboard-summary',
documents: 'http://localhost:8000/api/documents',
chartsData: 'http://localhost:8000/api/charts-data',
aiSuggestions: 'http://localhost:8000/api/ai-suggestions',
trainAI: 'http://localhost:8000/api/train-ai',
scrapeTrigger: 'http://localhost:8000/api/scrape-trigger'
};
// WebSocket connection - Updated for your backend
function connectWebSocket() {
try {
// For now, we'll use polling instead of WebSocket since your backend doesn't have WebSocket yet
console.log('WebSocket not implemented yet - using polling');
// Set up polling for updates every 30 seconds
setInterval(() => {
loadDashboardData();
}, 30000);
} catch (error) {
console.error('Failed to connect WebSocket:', error);
}
}
// Handle WebSocket messages
function handleWebSocketMessage(data) {
switch (data.type) {
case 'new_document':
showToast('سند جدید اضافه شد', 'success');
loadDashboardData();
break;
case 'scraping_completed':
showToast(`${data.documents_added} سند جدید اضافه شد`, 'success');
loadDashboardData();
break;
case 'ai_training_update':
showToast('آموزش هوش مصنوعی به‌روزرسانی شد', 'info');
loadAISuggestions();
break;
default:
console.log('Unknown WebSocket message type:', data.type);
}
}
// Load dashboard data with error handling
async function loadDashboardData() {
try {
console.log('Loading dashboard data...');
// Load stats
const statsResponse = await fetch(API_ENDPOINTS.dashboardSummary);
if (!statsResponse.ok) {
throw new Error(`Stats API error: ${statsResponse.status}`);
}
const stats = await statsResponse.json();
currentData.stats = stats;
updateStatsDisplay(stats);
// Load charts data
const chartsResponse = await fetch(API_ENDPOINTS.chartsData);
if (!chartsResponse.ok) {
throw new Error(`Charts API error: ${chartsResponse.status}`);
}
const charts = await chartsResponse.json();
currentData.charts = charts;
updateChartsDisplay(charts);
// Load documents
await loadDocuments();
// Load AI suggestions (if endpoint exists)
try {
await loadAISuggestions();
} catch (error) {
console.log('AI suggestions endpoint not available yet');
}
} catch (error) {
console.error('Error loading dashboard data:', error);
showToast('خطا در بارگذاری اطلاعات: ' + error.message, 'error');
// Show fallback data
showFallbackData();
}
}
// Show fallback data when API is not available
function showFallbackData() {
const fallbackStats = {
total_documents: 0,
documents_today: 0,
error_documents: 0,
average_score: 0
};
updateStatsDisplay(fallbackStats);
const fallbackCharts = {
trend_data: [],
category_data: []
};
updateChartsDisplay(fallbackCharts);
updateDocumentsTable([]);
}
// Update stats display with better error handling
function updateStatsDisplay(stats) {
const statsContainer = document.getElementById('stats');
const statsCards = [
{
title: 'کل اسناد',
value: stats.total_documents || 0,
icon: 'fas fa-file-alt',
type: 'gold',
change: '+12.5%'
},
{
title: 'اسناد جدید امروز',
value: stats.documents_today || 0,
icon: 'fas fa-file-plus',
type: 'silver',
change: '+8.3%'
},
{
title: 'اسناد با خطا',
value: stats.error_documents || 0,
icon: 'fas fa-exclamation-triangle',
type: 'bronze',
change: '-15.2%'
},
{
title: 'امتیاز میانگین',
value: stats.average_score || 0,
icon: 'fas fa-star',
type: 'platinum',
change: '+0.3'
}
];
statsContainer.innerHTML = statsCards.map(card => `
<div class="stat-card ${card.type}">
<div class="stat-header">
<div class="stat-title">${card.title}</div>
<div class="stat-icon">
<i class="${card.icon}"></i>
</div>
</div>
<div class="stat-value">${card.value.toLocaleString()}</div>
<div class="stat-change positive">
<i class="fas fa-arrow-up"></i>
${card.change}
</div>
</div>
`).join('');
}
// Update charts display with better error handling
function updateChartsDisplay(charts) {
const chartsContainer = document.getElementById('charts');
chartsContainer.innerHTML = `
<div class="chart-card">
<div class="chart-header">
<div class="chart-title">
<i class="fas fa-chart-line"></i>
روند جمع‌آوری اسناد
</div>
</div>
<div class="chart-container">
<canvas id="trendChart"></canvas>
<div class="chart-placeholder" id="trendPlaceholder" style="display: none;">
<i class="fas fa-chart-line" style="margin-left: 0.5rem;"></i>
نمودار در حال بارگذاری...
</div>
</div>
</div>
<div class="chart-card">
<div class="chart-header">
<div class="chart-title">
<i class="fas fa-chart-pie"></i>
توزیع دسته‌بندی
</div>
</div>
<div class="chart-container">
<canvas id="categoryChart"></canvas>
<div class="chart-placeholder" id="categoryPlaceholder" style="display: none;">
<i class="fas fa-chart-pie" style="margin-left: 0.5rem;"></i>
نمودار در حال بارگذاری...
</div>
</div>
</div>
`;
// Initialize charts after DOM update
setTimeout(() => {
initializeCharts(charts);
}, 100);
}
// Load documents with better error handling
async function loadDocuments(page = 1, filters = {}) {
try {
const params = new URLSearchParams({
limit: itemsPerPage,
offset: (page - 1) * itemsPerPage,
...filters
});
const response = await fetch(`${API_ENDPOINTS.documents}?${params}`);
if (!response.ok) {
throw new Error(`Documents API error: ${response.status}`);
}
const documents = await response.json();
currentData.documents = documents;
currentPage = page;
updateDocumentsTable(documents);
} catch (error) {
console.error('Error loading documents:', error);
showToast('خطا در بارگذاری اسناد: ' + error.message, 'error');
updateDocumentsTable([]);
}
}
// Update documents table with better error handling
function updateDocumentsTable(documents) {
const tableContainer = document.getElementById('documents');
if (!documents || documents.length === 0) {
tableContainer.innerHTML = `
<div class="table-header">
<div class="table-title">
<i class="fas fa-list"></i>
آخرین اسناد جمع‌آوری شده
</div>
<button class="btn btn-primary" type="button" onclick="triggerScraping()">
<i class="fas fa-sync"></i>
شروع جمع‌آوری
</button>
</div>
<div class="no-results">
<i class="fas fa-inbox" style="font-size: 3rem; color: var(--text-muted); margin-bottom: 1rem;"></i>
<p>هیچ سندی یافت نشد</p>
<p style="font-size: 0.9rem; color: var(--text-muted);">برای شروع، دکمه "شروع جمع‌آوری" را کلیک کنید</p>
</div>
`;
return;
}
const tableHTML = `
<div class="table-header">
<div class="table-title">
<i class="fas fa-list"></i>
آخرین اسناد جمع‌آوری شده
</div>
<button class="btn btn-primary" type="button" onclick="triggerScraping()">
<i class="fas fa-sync"></i>
جمع‌آوری جدید
</button>
</div>
<table class="table">
<thead>
<tr>
<th>عنوان سند</th>
<th>منبع</th>
<th>دسته‌بندی</th>
<th>امتیاز کیفیت</th>
<th>تاریخ</th>
<th>وضعیت</th>
<th>عملیات</th>
</tr>
</thead>
<tbody>
${documents.map(doc => `
<tr>
<td><strong>${doc.title || 'بدون عنوان'}</strong></td>
<td>${doc.source || 'نامشخص'}</td>
<td>${doc.category || 'نامشخص'}</td>
<td><strong style="color: var(--accent-primary);">${doc.final_score?.toFixed(1) || 'N/A'}</strong></td>
<td>${doc.publication_date || doc.extracted_at?.split('T')[0] || 'N/A'}</td>
<td><span class="status-badge ${doc.status || 'pending'}">${getStatusText(doc.status)}</span></td>
<td>
<button class="btn" type="button" onclick="viewDocument('${doc.id}')" style="font-size: 12px; padding: 0.25rem 0.5rem;">
<i class="fas fa-eye"></i>
مشاهده
</button>
</td>
</tr>
`).join('')}
</tbody>
</table>
`;
tableContainer.innerHTML = tableHTML;
}
// Load AI suggestions with better error handling
async function loadAISuggestions() {
try {
const response = await fetch(API_ENDPOINTS.aiSuggestions);
if (!response.ok) {
throw new Error(`AI suggestions API error: ${response.status}`);
}
const suggestions = await response.json();
currentData.aiSuggestions = suggestions;
updateAISuggestions(suggestions);
} catch (error) {
console.error('Error loading AI suggestions:', error);
// Don't show error toast for AI suggestions as it's optional
updateAISuggestions([]);
}
}
// Update AI suggestions
function updateAISuggestions(suggestions) {
const suggestionsContainer = document.getElementById('aiSuggestionsList');
if (!suggestions || suggestions.length === 0) {
suggestionsContainer.innerHTML = '<p style="text-align: center; color: var(--text-muted); padding: 2rem;">هیچ پیشنهاد هوش مصنوعی موجود نیست</p>';
return;
}
suggestionsContainer.innerHTML = suggestions.map(suggestion => `
<div class="ai-suggestion-item">
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.5rem;">
<h4 style="margin: 0; color: var(--text-primary);">${suggestion.title || 'بدون عنوان'}</h4>
<span class="confidence-badge ${getConfidenceClass(suggestion.confidence)}">
${getConfidenceText(suggestion.confidence)}
</span>
</div>
<p style="color: var(--text-secondary); margin-bottom: 0.5rem; font-size: 14px;">
پیشنهاد دسته‌بندی: <strong>${suggestion.predicted_category || 'نامشخص'}</strong>
</p>
<div style="display: flex; gap: 0.5rem;">
<button class="btn btn-primary" type="button" onclick="approveSuggestion('${suggestion.id}')" style="font-size: 12px; padding: 0.25rem 0.5rem;">
<i class="fas fa-check"></i>
تایید
</button>
<button class="btn" type="button" onclick="rejectSuggestion('${suggestion.id}')" style="font-size: 12px; padding: 0.25rem 0.5rem;">
<i class="fas fa-times"></i>
رد
</button>
</div>
</div>
`).join('');
}
// Trigger scraping function
async function triggerScraping() {
try {
showToast('در حال شروع جمع‌آوری اسناد...', 'info');
const response = await fetch(API_ENDPOINTS.scrapeTrigger, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ manual_trigger: true })
});
if (!response.ok) {
throw new Error(`Scraping API error: ${response.status}`);
}
const result = await response.json();
showToast('جمع‌آوری اسناد شروع شد', 'success');
// Reload data after a delay to show new documents
setTimeout(() => {
loadDashboardData();
}, 5000);
} catch (error) {
console.error('Error triggering scraping:', error);
showToast('خطا در شروع جمع‌آوری: ' + error.message, 'error');
}
}
// Helper functions
function getStatusText(status) {
const statusMap = {
'published': 'منتشر شده',
'pending': 'در حال بررسی',
'error': 'نیاز به اصلاح',
'processing': 'در حال پردازش',
'completed': 'تکمیل شده'
};
return statusMap[status] || status || 'نامشخص';
}
function getConfidenceClass(confidence) {
if (confidence >= 8) return 'confidence-high';
if (confidence >= 5) return 'confidence-medium';
return 'confidence-low';
}
function getConfidenceText(confidence) {
if (confidence >= 8) return 'عالی';
if (confidence >= 5) return 'متوسط';
return 'ضعیف';
}
// Modal functions
function viewDocument(documentId) {
const document = currentData.documents.find(doc => doc.id === documentId);
if (!document) {
showToast('سند یافت نشد', 'error');
return;
}
const modalBody = document.getElementById('modalBody');
modalBody.innerHTML = `
<div style="margin-bottom: 1rem;">
<h4 style="color: var(--text-primary); margin-bottom: 0.5rem;">${document.title || 'بدون عنوان'}</h4>
<p style="color: var(--text-secondary); font-size: 14px;">${document.document_number || 'شماره سند موجود نیست'}</p>
</div>
<div style="margin-bottom: 1rem;">
<h5 style="color: var(--text-primary); margin-bottom: 0.5rem;">جزئیات سند</h5>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; font-size: 14px;">
<div><strong>منبع:</strong> ${document.source || 'نامشخص'}</div>
<div><strong>دسته‌بندی:</strong> ${document.category || 'نامشخص'}</div>
<div><strong>امتیاز کیفیت:</strong> ${document.final_score?.toFixed(1) || 'N/A'}</div>
<div><strong>وضعیت:</strong> <span class="status-badge ${document.status || 'pending'}">${getStatusText(document.status)}</span></div>
</div>
</div>
<div style="margin-bottom: 1rem;">
<h5 style="color: var(--text-primary); margin-bottom: 0.5rem;">متن سند</h5>
<div style="background: var(--surface-variant); padding: 1rem; border-radius: var(--radius-md); max-height: 200px; overflow-y: auto; font-size: 14px; line-height: 1.6;">
${document.full_text || document.content || 'متن سند موجود نیست'}
</div>
</div>
`;
document.getElementById('documentModal').style.display = 'flex';
}
function closeDocumentModal() {
document.getElementById('documentModal').style.display = 'none';
}
function approveDocument() {
// Implementation for document approval
showToast('سند تایید شد', 'success');
closeDocumentModal();
}
function rejectDocument() {
// Implementation for document rejection
showToast('سند رد شد', 'warning');
closeDocumentModal();
}
// AI suggestion functions with better error handling
async function approveSuggestion(suggestionId) {
try {
const response = await fetch(API_ENDPOINTS.trainAI, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
document_id: suggestionId,
feedback_type: 'approved',
feedback_score: 10,
feedback_text: 'تایید شده'
})
});
if (!response.ok) {
throw new Error(`Training API error: ${response.status}`);
}
showToast('پیشنهاد تایید شد', 'success');
loadAISuggestions();
} catch (error) {
console.error('Error approving suggestion:', error);
showToast('خطا در تایید پیشنهاد: ' + error.message, 'error');
}
}
async function rejectSuggestion(suggestionId) {
try {
const response = await fetch(API_ENDPOINTS.trainAI, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
document_id: suggestionId,
feedback_type: 'rejected',
feedback_score: 0,
feedback_text: 'رد شده'
})
});
if (!response.ok) {
throw new Error(`Training API error: ${response.status}`);
}
showToast('پیشنهاد رد شد', 'warning');
loadAISuggestions();
} catch (error) {
console.error('Error rejecting suggestion:', error);
showToast('خطا در رد پیشنهاد: ' + error.message, 'error');
}
}
// Toast notification function
function showToast(message, type = 'info') {
const toastContainer = document.getElementById('toastContainer');
const toastId = 'toast-' + Date.now();
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.id = toastId;
toast.innerHTML = `
<div class="toast-header">
<div class="toast-title">${type === 'success' ? 'موفقیت' : type === 'error' ? 'خطا' : type === 'warning' ? 'هشدار' : 'اطلاعات'}</div>
<button class="toast-close" type="button" onclick="removeToast('${toastId}')" aria-label="بستن">
<i class="fas fa-times"></i>
</button>
</div>
<div class="toast-message">${message}</div>
`;
toastContainer.appendChild(toast);
// Auto remove after 5 seconds
setTimeout(() => removeToast(toastId), 5000);
}
function removeToast(toastId) {
const toast = document.getElementById(toastId);
if (toast) {
toast.style.animation = 'slideOut 0.3s ease';
setTimeout(() => toast.remove(), 300);
}
}
// Chart initialization
function initializeCharts(chartsData) {
// This will be implemented when Chart.js is loaded
console.log('Charts data:', chartsData);
}
// Search functionality
function setupSearch() {
const searchInput = document.getElementById('searchInput');
let searchTimeout;
searchInput.addEventListener('input', (e) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
const term = e.target.value.toLowerCase().trim();
if (term) {
loadDocuments(1, { search: term });
} else {
loadDocuments(1);
}
}, 300);
});
}
// Initialize dashboard
document.addEventListener('DOMContentLoaded', function() {
// Show loading screen
setTimeout(() => {
document.getElementById('loadingScreen').classList.add('hidden');
document.getElementById('dashboard').classList.add('loaded');
// Initialize components
setTimeout(() => {
connectWebSocket();
loadDashboardData();
setupSearch();
}, 500);
}, 1500);
});
</script>
</body>
</html>