| <!DOCTYPE html> |
| <html lang="fa" dir="rtl"> |
|
|
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>سیستم مدیریت ضمانتنامههای بانکی</title> |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.rtl.min.css"> |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css"> |
| <link rel="stylesheet" |
| href="https://cdn.jsdelivr.net/npm/persian-datepicker@1.2.0/dist/css/persian-datepicker.min.css"> |
| <style> |
| :root { |
| --primary-color: #2c3e50; |
| --secondary-color: #3498db; |
| --success-color: #27ae60; |
| --warning-color: #f39c12; |
| --danger-color: #e74c3c; |
| --light-color: #ecf0f1; |
| --dark-color: #2c3e50; |
| } |
| |
| body { |
| font-family: 'Vazirmatn', 'Tahoma', sans-serif; |
| background-color: #f5f7fa; |
| color: var(--dark-color); |
| } |
| |
| .sidebar { |
| background-color: var(--primary-color); |
| color: white; |
| min-height: 100vh; |
| position: sticky; |
| top: 0; |
| } |
| |
| .sidebar .nav-link { |
| color: rgba(255, 255, 255, 0.8); |
| padding: 10px 15px; |
| margin: 5px 0; |
| border-radius: 5px; |
| transition: all 0.3s; |
| } |
| |
| .sidebar .nav-link:hover, |
| .sidebar .nav-link.active { |
| background-color: rgba(255, 255, 255, 0.1); |
| color: white; |
| } |
| |
| .main-content { |
| background-color: white; |
| border-radius: 10px; |
| box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); |
| padding: 20px; |
| margin: 20px; |
| } |
| |
| .header { |
| background-color: white; |
| padding: 10px 20px; |
| border-bottom: 1px solid #eee; |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| } |
| |
| .card { |
| border: none; |
| border-radius: 10px; |
| box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); |
| margin-bottom: 20px; |
| } |
| |
| .card-header { |
| background-color: var(--light-color); |
| border-radius: 10px 10px 0 0 !important; |
| font-weight: bold; |
| } |
| |
| .btn-primary { |
| background-color: var(--secondary-color); |
| border-color: var(--secondary-color); |
| } |
| |
| .btn-success { |
| background-color: var(--success-color); |
| border-color: var(--success-color); |
| } |
| |
| .btn-warning { |
| background-color: var(--warning-color); |
| border-color: var(--warning-color); |
| } |
| |
| .btn-danger { |
| background-color: var(--danger-color); |
| border-color: var(--danger-color); |
| } |
| |
| .status-badge { |
| padding: 5px 10px; |
| border-radius: 20px; |
| font-size: 0.8rem; |
| font-weight: bold; |
| } |
| |
| .status-active { |
| background-color: rgba(46, 204, 113, 0.2); |
| color: var(--success-color); |
| } |
| |
| .status-renewed { |
| background-color: rgba(52, 152, 219, 0.2); |
| color: var(--secondary-color); |
| } |
| |
| .status-released { |
| background-color: rgba(155, 89, 182, 0.2); |
| color: #9b59b6; |
| } |
| |
| .status-captured { |
| background-color: rgba(241, 196, 15, 0.2); |
| color: var(--warning-color); |
| } |
| |
| .status-cancelled { |
| background-color: rgba(231, 76, 60, 0.2); |
| color: var(--danger-color); |
| } |
| |
| .table-responsive { |
| overflow-x: auto; |
| } |
| |
| .table { |
| margin-bottom: 0; |
| } |
| |
| .table th, |
| .table td { |
| vertical-align: middle; |
| } |
| |
| .modal-dialog { |
| max-width: 800px; |
| } |
| |
| .form-control, |
| .form-select { |
| border-radius: 5px; |
| border: 1px solid #ddd; |
| } |
| |
| .form-control:focus, |
| .form-select:focus { |
| border-color: var(--secondary-color); |
| box-shadow: 0 0 0 0.25rem rgba(52, 152, 219, 0.25); |
| } |
| |
| .alert { |
| border-radius: 5px; |
| padding: 10px 15px; |
| margin-bottom: 20px; |
| } |
| |
| .pagination { |
| justify-content: center; |
| } |
| |
| .search-box { |
| position: relative; |
| } |
| |
| .search-box input { |
| padding-left: 35px; |
| } |
| |
| .search-box i { |
| position: absolute; |
| left: 10px; |
| top: 50%; |
| transform: translateY(-50%); |
| color: #999; |
| } |
| |
| .file-upload { |
| border: 2px dashed #ddd; |
| border-radius: 5px; |
| padding: 20px; |
| text-align: center; |
| cursor: pointer; |
| transition: all 0.3s; |
| } |
| |
| .file-upload:hover { |
| border-color: var(--secondary-color); |
| background-color: rgba(52, 152, 219, 0.05); |
| } |
| |
| .file-list { |
| max-height: 200px; |
| overflow-y: auto; |
| border: 1px solid #eee; |
| border-radius: 5px; |
| padding: 10px; |
| } |
| |
| .file-item { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| padding: 5px 0; |
| border-bottom: 1px solid #eee; |
| } |
| |
| .file-item:last-child { |
| border-bottom: none; |
| } |
| |
| .expiration-warning { |
| background-color: rgba(243, 156, 18, 0.1); |
| border-left: 4px solid var(--warning-color); |
| } |
| |
| .expiration-danger { |
| background-color: rgba(231, 76, 60, 0.1); |
| border-left: 4px solid var(--danger-color); |
| } |
| |
| .chart-container { |
| position: relative; |
| height: 300px; |
| } |
| |
| @media (max-width: 768px) { |
| .sidebar { |
| position: relative; |
| min-height: auto; |
| } |
| |
| .main-content { |
| margin: 10px; |
| } |
| } |
| |
| .persian-datepicker { |
| direction: rtl; |
| } |
| |
| .built-with { |
| font-size: 0.8rem; |
| color: #7f8c8d; |
| text-align: center; |
| padding: 10px; |
| background-color: #ecf0f1; |
| border-top: 1px solid #ddd; |
| } |
| |
| .built-with a { |
| color: var(--secondary-color); |
| text-decoration: none; |
| } |
| </style> |
| </head> |
|
|
| <body> |
| <div class="container-fluid"> |
| <div class="row"> |
| |
| <div class="col-md-3 col-lg-2 sidebar"> |
| <div class="p-3"> |
| <h4 class="text-center mb-4">مدیریت ضمانتنامهها</h4> |
| <ul class="nav flex-column" id="sidebarMenu"> |
| <li class="nav-item"> |
| <a class="nav-link active" href="#" data-page="dashboard"> |
| <i class="bi bi-speedometer2 me-2"></i> |
| داشبورد |
| </a> |
| </li> |
| <li class="nav-item"> |
| <a class="nav-link" href="#" data-page="guarantees"> |
| <i class="bi bi-file-earmark-text me-2"></i> |
| ضمانتنامهها |
| </a> |
| </li> |
| <li class="nav-item"> |
| <a class="nav-link" href="#" data-page="contractors"> |
| <i class="bi bi-people me-2"></i> |
| پیمانکاران |
| </a> |
| </li> |
| <li class="nav-item"> |
| <a class="nav-link" href="#" data-page="banks"> |
| <i class="bi bi-bank me-2"></i> |
| بانکها و شعب |
| </a> |
| </li> |
| <li class="nav-item"> |
| <a class="nav-link" href="#" data-page="reports"> |
| <i class="bi bi-graph-up me-2"></i> |
| گزارشها |
| </a> |
| </li> |
| <li class="nav-item"> |
| <a class="nav-link" href="#" data-page="backup"> |
| <i class="bi bi-hdd me-2"></i> |
| پشتیبانگیری |
| </a> |
| </li> |
| <li class="nav-item"> |
| <a class="nav-link" href="#" data-page="settings"> |
| <i class="bi bi-gear me-2"></i> |
| تنظیمات |
| </a> |
| </li> |
| </ul> |
| </div> |
| </div> |
|
|
| |
| <div class="col-md-9 col-lg-10"> |
| <div class="header"> |
| <div> |
| <button class="btn btn-sm btn-outline-secondary" id="toggleSidebar"> |
| <i class="bi bi-list"></i> |
| </button> |
| <span class="me-3">کاربر: مدیر سیستم</span> |
| </div> |
| <div> |
| <span id="currentDate"></span> |
| <button class="btn btn-sm btn-outline-danger ms-2" id="logoutBtn"> |
| <i class="bi bi-box-arrow-left"></i> خروج |
| </button> |
| </div> |
| </div> |
|
|
| <div class="main-content" id="pageContent"> |
| |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="modal fade" id="loginModal" tabindex="-1" aria-hidden="true"> |
| <div class="modal-dialog modal-dialog-centered"> |
| <div class="modal-content"> |
| <div class="modal-header"> |
| <h5 class="modal-title">ورود به سیستم</h5> |
| </div> |
| <div class="modal-body"> |
| <form id="loginForm"> |
| <div class="mb-3"> |
| <label for="username" class="form-label">نام کاربری</label> |
| <input type="text" class="form-control" id="username" required> |
| </div> |
| <div class="mb-3"> |
| <label for="password" class="form-label">رمز عبور</label> |
| <input type="password" class="form-control" id="password" required> |
| </div> |
| <div class="d-grid"> |
| <button type="submit" class="btn btn-primary">ورود</button> |
| </div> |
| </form> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="built-with"> |
| Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a> |
| </div> |
|
|
| |
| <script src="https://cdn.jsdelivr.net/npm/persian-date@1.1.0/dist/persian-date.min.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/persian-datepicker@1.2.0/dist/js/persian-datepicker.min.js"></script> |
|
|
| |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
|
|
| |
| <script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script> |
|
|
| |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf-autotable/3.5.29/jspdf.plugin.autotable.min.js"></script> |
|
|
| |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script> |
|
|
| |
| <script src="https://cdn.jsdelivr.net/npm/js-md5@0.7.3/src/md5.min.js"></script> |
|
|
| <script> |
| // Database simulation (in a real app, this would be SQLite) |
| let db = { |
| users: [ |
| { id: 1, username: 'admin', password: '5f4dcc3b5aa765d61d8327deb882cf99' } // password: "password" |
| ], |
| contractors: [], |
| banks: [], |
| guarantees: [], |
| files: [] |
| }; |
| |
| // Current user |
| let currentUser = null; |
| |
| // Initialize the app |
| document.addEventListener('DOMContentLoaded', function() { |
| // Check if user is logged in |
| const savedUser = localStorage.getItem('currentUser'); |
| if (savedUser) { |
| currentUser = JSON.parse(savedUser); |
| loadPage('dashboard'); |
| updateCurrentDate(); |
| checkExpiringGuarantees(); |
| } else { |
| showLogin(); |
| } |
| |
| // Set up event listeners |
| setupEventListeners(); |
| |
| // Initialize Persian datepickers |
| initializeDatepickers(); |
| |
| // Load sample data for demo |
| loadSampleData(); |
| }); |
| |
| function setupEventListeners() { |
| // Sidebar navigation - FIXED: Now properly working |
| document.getElementById('sidebarMenu').addEventListener('click', function(e) { |
| if (e.target.classList.contains('nav-link')) { |
| e.preventDefault(); |
| const page = e.target.getAttribute('data-page'); |
| loadPage(page); |
| } |
| }); |
| |
| // Login form |
| document.getElementById('loginForm')?.addEventListener('submit', function(e) { |
| e.preventDefault(); |
| login(); |
| }); |
| |
| // Logout button |
| document.getElementById('logoutBtn')?.addEventListener('click', logout); |
| |
| // Toggle sidebar for mobile |
| document.getElementById('toggleSidebar')?.addEventListener('click', function() { |
| document.querySelector('.sidebar').classList.toggle('d-none'); |
| document.querySelector('.col-md-9').classList.toggle('col-md-12'); |
| }); |
| } |
| |
| function initializeDatepickers() { |
| // Initialize Persian datepickers |
| if (typeof persianDatepicker !== 'undefined') { |
| document.querySelectorAll('.persian-datepicker').forEach(input => { |
| $(input).persianDatepicker({ |
| format: 'YYYY/MM/DD', |
| autoClose: true, |
| persianNumbers: true, |
| initialValue: false |
| }); |
| }); |
| } |
| } |
| |
| function loadSampleData() { |
| // Add sample banks |
| if (db.banks.length === 0) { |
| db.banks = [ |
| { id: 1, name: 'بانک ملی ایران', branch: 'شعبه مرکزی', code: '1001', phone: '02112345678', address: 'تهران، خیابان جمهوری' }, |
| { id: 2, name: 'بانک ملت', branch: 'شعبه ولیعصر', code: '2001', phone: '02187654321', address: 'تهران، خیابان ولیعصر' }, |
| { id: 3, name: 'بانک سپه', branch: 'شعبه انقلاب', code: '3001', phone: '02123456789', address: 'تهران، خیابان انقلاب' } |
| ]; |
| } |
| |
| // Add sample contractors |
| if (db.contractors.length === 0) { |
| db.contractors = [ |
| { id: 1, name: 'شرکت ساختمانی پیشرو', nationalId: '1234567890', economicCode: '123456789', contactPerson: 'علی احمدی', phone: '02112345678', mobile: '09121234567', email: 'info@pishro.com', address: 'تهران، خیابان آزادی' }, |
| { id: 2, name: 'شرکت عمرانی توسعه', nationalId: '1098765432', economicCode: '987654321', contactPerson: 'محمدرضا کریمی', phone: '02187654321', mobile: '09127654321', email: 'info@tosee.com', address: 'تهران، خیابان ولیعصر' } |
| ]; |
| } |
| |
| // Add sample guarantees |
| if (db.guarantees.length === 0) { |
| const today = new persianDate().format('YYYY/MM/DD'); |
| const futureDate = new persianDate().add('day', 30).format('YYYY/MM/DD'); |
| const pastDate = new persianDate().subtract('day', 30).format('YYYY/MM/DD'); |
| |
| db.guarantees = [ |
| { id: 1, number: 'BG-2023-001', type: 'تندر', bankId: 1, amount: 500000000, issueDate: today, expiryDate: futureDate, status: 'active', contractorId: 1, contractNumber: 'C-2023-001', contractTitle: 'پروژه ساخت ساختمان اداری', description: 'ضمانتنامه برای شرکت در مناقصه پروژه ساخت ساختمان اداری', files: [] }, |
| { id: 2, number: 'BG-2023-002', type: 'حسن انجام کار', bankId: 2, amount: 800000000, issueDate: pastDate, expiryDate: today, status: 'active', contractorId: 2, contractNumber: 'C-2023-002', contractTitle: 'پروژه احداث پل', description: 'ضمانتنامه حسن انجام کار برای پروژه احداث پل', files: [] }, |
| { id: 3, number: 'BG-2023-003', type: 'پیشپرداخت', bankId: 3, amount: 300000000, issueDate: pastDate, expiryDate: pastDate, status: 'released', contractorId: 1, contractNumber: 'C-2023-003', contractTitle: 'پروژه نوسازی مدرسه', description: 'ضمانتنامه پیشپرداخت برای پروژه نوسازی مدرسه', files: [] } |
| ]; |
| } |
| } |
| |
| function showLogin() { |
| const loginModal = new bootstrap.Modal(document.getElementById('loginModal')); |
| loginModal.show(); |
| } |
| |
| function login() { |
| const username = document.getElementById('username').value; |
| const password = document.getElementById('password').value; |
| |
| // Simple hash function for demo (in real app, use proper hashing) |
| const hash = md5(password); |
| |
| const user = db.users.find(u => u.username === username && u.password === hash); |
| |
| if (user) { |
| currentUser = user; |
| localStorage.setItem('currentUser', JSON.stringify(user)); |
| bootstrap.Modal.getInstance(document.getElementById('loginModal')).hide(); |
| loadPage('dashboard'); |
| updateCurrentDate(); |
| checkExpiringGuarantees(); |
| } else { |
| alert('نام کاربری یا رمز عبور اشتباه است'); |
| } |
| } |
| |
| function logout() { |
| currentUser = null; |
| localStorage.removeItem('currentUser'); |
| showLogin(); |
| } |
| |
| function updateCurrentDate() { |
| const now = new persianDate(); |
| document.getElementById('currentDate').textContent = now.format('dddd D MMMM YYYY'); |
| } |
| |
| function checkExpiringGuarantees() { |
| const now = new persianDate(); |
| const alerts = []; |
| |
| db.guarantees.forEach(guarantee => { |
| const expiryDate = new persianDate(guarantee.expiryDate); |
| const daysLeft = expiryDate.diff(now, 'days'); |
| |
| if (daysLeft <= 30 && daysLeft >= 0 && guarantee.status === 'active') { |
| alerts.push({ |
| number: guarantee.number, |
| contractor: db.contractors.find(c => c.id === guarantee.contractorId)?.name || 'نامشخص', |
| expiryDate: guarantee.expiryDate, |
| daysLeft: daysLeft |
| }); |
| } |
| }); |
| |
| if (alerts.length > 0) { |
| showAlerts(alerts); |
| } |
| } |
| |
| function showAlerts(alerts) { |
| let html = '<div class="alert alert-warning">'; |
| html += '<h6>ضمانتنامههای نزدیک به سررسید:</h6>'; |
| html += '<ul class="mb-0">'; |
| |
| alerts.forEach(alert => { |
| html += `<li>${alert.number} - ${alert.contractor} (${alert.daysLeft} روز باقیمانده تا ${alert.expiryDate})</li>`; |
| }); |
| |
| html += '</ul></div>'; |
| |
| document.getElementById('alertModalBody').innerHTML = html; |
| const alertModal = new bootstrap.Modal(document.getElementById('alertModal')); |
| alertModal.show(); |
| } |
| |
| function loadPage(page) { |
| // Update active nav link |
| document.querySelectorAll('.nav-link').forEach(link => { |
| link.classList.remove('active'); |
| if (link.getAttribute('data-page') === page) { |
| link.classList.add('active'); |
| } |
| }); |
| |
| // Load page content |
| switch(page) { |
| case 'dashboard': |
| loadDashboard(); |
| break; |
| case 'guarantees': |
| loadGuarantees(); |
| break; |
| case 'contractors': |
| loadContractors(); |
| break; |
| case 'banks': |
| loadBanks(); |
| break; |
| case 'reports': |
| loadReports(); |
| break; |
| case 'backup': |
| loadBackup(); |
| break; |
| case 'settings': |
| loadSettings(); |
| break; |
| default: |
| loadDashboard(); |
| } |
| } |
| |
| function loadDashboard() { |
| const now = new persianDate(); |
| const activeGuarantees = db.guarantees.filter(g => g.status === 'active'); |
| const expiringSoon = activeGuarantees.filter(g => { |
| const expiryDate = new persianDate(g.expiryDate); |
| return expiryDate.diff(now, 'days') <= 30 && expiryDate.diff(now, 'days') >= 0; |
| }); |
| const expired = activeGuarantees.filter(g => { |
| const expiryDate = new persianDate(g.expiryDate); |
| return expiryDate.diff(now, 'days') < 0; |
| }); |
| |
| const statusCounts = { |
| active: db.guarantees.filter(g => g.status === 'active').length, |
| renewed: db.guarantees.filter(g => g.status === 'renewed').length, |
| released: db.guarantees.filter(g => g.status === 'released').length, |
| captured: db.guarantees.filter(g => g.status === 'captured').length, |
| cancelled: db.guarantees.filter(g => g.status === 'cancelled').length |
| }; |
| |
| const totalAmount = db.guarantees.reduce((sum, g) => sum + g.amount, 0); |
| |
| let html = ` |
| <div class="row mb-4"> |
| <div class="col-md-12"> |
| <h4>داشبورد مدیریت ضمانتنامهها</h4> |
| </div> |
| </div> |
| |
| <div class="row mb-4"> |
| <div class="col-md-3"> |
| <div class="card text-center"> |
| <div class="card-body"> |
| <h5 class="card-title">کل ضمانتنامهها</h5> |
| <p class="card-text fs-3">${db.guarantees.length}</p> |
| </div> |
| </div> |
| </div> |
| <div class="col-md-3"> |
| <div class="card text-center"> |
| <div class="card-body"> |
| <h5 class="card-title">ضمانتنامههای فعال</h5> |
| <p class="card-text fs-3">${statusCounts.active}</p> |
| </div> |
| </div> |
| </div> |
| <div class="col-md-3"> |
| <div class="card text-center"> |
| <div class="card-body"> |
| <h5 class="card-title">نزدیک به سررسید</h5> |
| <p class="card-text fs-3">${expiringSoon.length}</p> |
| </div> |
| </div> |
| </div> |
| <div class="col-md-3"> |
| <div class="card text-center"> |
| <div class="card-body"> |
| <h5 class="card-title">منقضی شده</h5> |
| <p class="card-text fs-3">${expired.length}</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="row mb-4"> |
| <div class="col-md-6"> |
| <div class="card"> |
| <div class="card-header"> |
| وضعیت ضمانتنامهها |
| </div> |
| <div class="card-body"> |
| <canvas id="statusChart" width="400" height="300"></canvas> |
| </div> |
| </div> |
| </div> |
| <div class="col-md-6"> |
| <div class="card"> |
| <div class="card-header"> |
| جمع مبالغ (ریال) |
| </div> |
| <div class="card-body"> |
| <canvas id="amountChart" width="400" height="300"></canvas> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="row"> |
| <div class="col-md-12"> |
| <div class="card"> |
| <div class="card-header d-flex justify-content-between align-items-center"> |
| ضمانتنامههای نزدیک به سررسید |
| <button class="btn btn-sm btn-primary" onclick="loadPage('guarantees')">مشاهده همه</button> |
| </div> |
| <div class="card-body"> |
| <div class="table-responsive"> |
| <table class="table table-hover"> |
| <thead> |
| <tr> |
| <th>شماره</th> |
| <th>پیمانکار</th> |
| <th>بانک</th> |
| <th>مبلغ</th> |
| <th>تاریخ سررسید</th> |
| <th>روزهای باقیمانده</th> |
| <th>وضعیت</th> |
| </tr> |
| </thead> |
| <tbody> |
| `; |
| |
| if (expiringSoon.length > 0) { |
| expiringSoon.forEach(g => { |
| const bank = db.banks.find(b => b.id === g.bankId); |
| const contractor = db.contractors.find(c => c.id === g.contractorId); |
| const expiryDate = new persianDate(g.expiryDate); |
| const now = new persianDate(); |
| const daysLeft = expiryDate.diff(now, 'days'); |
| |
| html += ` |
| <tr class="${daysLeft <= 7 ? 'expiration-danger' : 'expiration-warning'}"> |
| <td>${g.number}</td> |
| <td>${contractor ? contractor.name : 'نامشخص'}</td> |
| <td>${bank ? `${bank.name} - ${bank.branch}` : 'نامشخص'}</td> |
| <td>${g.amount.toLocaleString()}</td> |
| <td>${g.expiryDate}</td> |
| <td>${daysLeft}</td> |
| <td><span class="status-badge status-active">فعال</span></td> |
| </tr> |
| `; |
| }); |
| } else { |
| html += ` |
| <tr> |
| <td colspan="7" class="text-center">ضمانتنامه نزدیک به سررسید وجود ندارد</td> |
| </tr> |
| `; |
| } |
| |
| html += ` |
| </tbody> |
| </table> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| `; |
| |
| document.getElementById('pageContent').innerHTML = html; |
| |
| // Initialize charts |
| initStatusChart(statusCounts); |
| initAmountChart(); |
| } |
| |
| function initStatusChart(statusCounts) { |
| const ctx = document.getElementById('statusChart').getContext('2d'); |
| new Chart(ctx, { |
| type: 'doughnut', |
| data: { |
| labels: ['فعال', 'تمدید شده', 'آزاد شده', 'ضبط شده', 'ابطال شده'], |
| datasets: [{ |
| data: [ |
| statusCounts.active, |
| statusCounts.renewed, |
| statusCounts.released, |
| statusCounts.captured, |
| statusCounts.cancelled |
| ], |
| backgroundColor: [ |
| 'rgba(46, 204, 113, 0.7)', |
| 'rgba(52, 152, 219, 0.7)', |
| 'rgba(155, 89, 182, 0.7)', |
| 'rgba(241, 196, 15, 0.7)', |
| 'rgba(231, 76, 60, 0.7)' |
| ], |
| borderColor: [ |
| 'rgba(46, 204, 113, 1)', |
| 'rgba(52, 152, 219, 1)', |
| 'rgba(155, 89, 182, 1)', |
| 'rgba(241, 196, 15, 1)', |
| 'rgba(231, 76, 60, 1)' |
| ], |
| borderWidth: 1 |
| }] |
| }, |
| options: { |
| responsive: true, |
| plugins: { |
| legend: { |
| position: 'bottom', |
| } |
| } |
| } |
| }); |
| } |
| |
| function initAmountChart() { |
| const typeAmounts = {}; |
| db.guarantees.forEach(g => { |
| if (!typeAmounts[g.type]) { |
| typeAmounts[g.type] = 0; |
| } |
| typeAmounts[g.type] += g.amount; |
| }); |
| |
| const ctx = document.getElementById('amountChart').getContext('2d'); |
| new Chart(ctx, { |
| type: 'bar', |
| data: { |
| labels: Object.keys(typeAmounts), |
| datasets: [{ |
| label: 'مبلغ (ریال)', |
| data: Object.values(typeAmounts), |
| backgroundColor: 'rgba(52, 152, 219, 0.7)', |
| borderColor: 'rgba(52, 152, 219, 1)', |
| borderWidth: 1 |
| }] |
| }, |
| options: { |
| responsive: true, |
| scales: { |
| y: { |
| beginAtZero: true |
| } |
| }, |
| plugins: { |
| legend: { |
| display: false |
| } |
| } |
| } |
| }); |
| } |
| |
| function loadGuarantees() { |
| let html = ` |
| <div class="row mb-4"> |
| <div class="col-md-6"> |
| <h4>مدیریت ضمانتنامهها</h4> |
| </div> |
| <div class="col-md-6 text-start"> |
| <button class="btn btn-primary" onclick="showGuaranteeModal()"> |
| <i class="bi bi-plus-circle me-2"></i> اضافه کردن ضمانتنامه جدید |
| </button> |
| </div> |
| </div> |
| |
| <div class="card"> |
| <div class="card-header"> |
| لیست ضمانتنامهها |
| </div> |
| <div class="card-body"> |
| <div class="row mb-3"> |
| <div class="col-md-4"> |
| <div class="search-box"> |
| <i class="bi bi-search"></i> |
| <input type="text" class="form-control" id="searchGuarantees" placeholder="جستجوی ضمانتنامه..."> |
| </div> |
| </div> |
| <div class="col-md-4"> |
| <select class="form-select" id="filterStatus"> |
| <option value="">همه وضعیتها</option> |
| <option value="active">فعال</option> |
| <option value="renewed">تمدید شده</option> |
| <option value="released">آزاد شده</option> |
| <option value="captured">ضبط شده</option> |
| <option value="cancelled">ابطال شده</option> |
| </select> |
| </div> |
| <div class="col-md-4"> |
| <select class="form-select" id="filterBank"> |
| <option value="">همه بانکها</option> |
| ${db.banks.map(bank => `<option value="${bank.id}">${bank.name} - ${bank.branch}</option>`).join('')} |
| </select> |
| </div> |
| </div> |
| |
| <div class="table-responsive"> |
| <table class="table table-hover"> |
| <thead> |
| <tr> |
| <th>شماره</th> |
| <th>نوع</th> |
| <th>پیمانکار</th> |
| <th>بانک</th> |
| <th>مبلغ</th> |
| <th>تاریخ صدور</th> |
| <th>تاریخ سررسید</th> |
| <th>وضعیت</th> |
| <th>عملیات</th> |
| </tr> |
| </thead> |
| <tbody id="guaranteesTableBody"> |
| ${db.guarantees.map(g => { |
| const bank = db.banks.find(b => b.id === g.bankId); |
| const contractor = db.contractors.find(c => c.id === g.contractorId); |
| return ` |
| <tr> |
| <td>${g.number}</td> |
| <td>${g.type}</td> |
| <td>${contractor ? contractor.name : 'نامشخص'}</td> |
| <td>${bank ? `${bank.name} - ${bank.branch}` : 'نامشخص'}</td> |
| <td>${g.amount.toLocaleString()}</td> |
| <td>${g.issueDate}</td> |
| <td>${g.expiryDate}</td> |
| <td><span class="status-badge status-${g.status}">${getStatusText(g.status)}</span></td> |
| <td> |
| <button class="btn btn-sm btn-outline-primary" onclick="editGuarantee(${g.id})"> |
| <i class="bi bi-pencil"></i> |
| </button> |
| <button class="btn btn-sm btn-outline-danger" onclick="deleteGuarantee(${g.id})"> |
| <i class="bi bi-trash"></i> |
| </button> |
| </td> |
| </tr> |
| `; |
| }).join('')} |
| </tbody> |
| </table> |
| </div> |
| |
| <div class="d-flex justify-content-between align-items-center"> |
| <div> |
| <span>نمایش 1 تا ${db.guarantees.length} از ${db.guarantees.length} مورد</span> |
| </div> |
| <nav> |
| <ul class="pagination"> |
| <li class="page-item disabled"> |
| <a class="page-link" href="#" aria-label="Previous"> |
| <span aria-hidden="true">«</span> |
| </a> |
| </li> |
| <li class="page-item active"><a class="page-link" href="#">1</a></li> |
| <li class="page-item disabled"> |
| <a class="page-link" href="#" aria-label="Next"> |
| <span aria-hidden="true">»</span> |
| </a> |
| </li> |
| </ul> |
| </nav> |
| </div> |
| </div> |
| </div> |
| `; |
| |
| document.getElementById('pageContent').innerHTML = html; |
| |
| // Set up search and filter functionality |
| document.getElementById('searchGuarantees').addEventListener('input', filterGuarantees); |
| document.getElementById('filterStatus').addEventListener('change', filterGuarantees); |
| document.getElementById('filterBank').addEventListener('change', filterGuarantees); |
| } |
| |
| function loadContractors() { |
| let html = ` |
| <div class="row mb-4"> |
| <div class="col-md-6"> |
| <h4>مدیریت پیمانکاران</h4> |
| </div> |
| <div class="col-md-6 text-start"> |
| <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#contractorModal" onclick="resetContractorForm()"> |
| <i class="bi bi-plus-circle me-2"></i> اضافه کردن پیمانکار جدید |
| </button> |
| </div> |
| </div> |
| |
| <div class="card"> |
| <div class="card-header"> |
| لیست پیمانکاران |
| </div> |
| <div class="card-body"> |
| <div class="row mb-3"> |
| <div class="col-md-6"> |
| <div class="search-box"> |
| <i class="bi bi-search"></i> |
| <input type="text" class="form-control" id="searchContractors" placeholder="جستجوی پیمانکار..."> |
| </div> |
| </div> |
| </div> |
| |
| <div class="table-responsive"> |
| <table class="table table-hover"> |
| <thead> |
| <tr> |
| <th>نام شرکت</th> |
| <th>شناسه ملی</th> |
| <th>کد اقتصادی</th> |
| <th>مسئول</th> |
| <th>تلفن</th> |
| <th>موبایل</th> |
| <th>عملیات</th> |
| </tr> |
| </thead> |
| <tbody id="contractorsTableBody"> |
| ${db.contractors.map(c => ` |
| <tr> |
| <td>${c.name}</td> |
| <td>${c.nationalId || '-'}</td> |
| <td>${c.economicCode || '-'}</td> |
| <td>${c.contactPerson || '-'}</td> |
| <td>${c.phone || '-'}</td> |
| <td>${c.mobile || '-'}</td> |
| <td> |
| <button class="btn btn-sm btn-outline-primary" onclick="editContractor(${c.id})"> |
| <i class="bi bi-pencil"></i> |
| </button> |
| <button class="btn btn-sm btn-outline-danger" onclick="deleteContractor(${c.id})"> |
| <i class="bi bi-trash"></i> |
| </button> |
| </td> |
| </tr> |
| `).join('')} |
| </tbody> |
| </table> |
| </div> |
| </div> |
| </div> |
| `; |
| |
| document.getElementById('pageContent').innerHTML = html; |
| |
| // Set up search functionality |
| document.getElementById('searchContractors').addEventListener('input', function() { |
| const searchTerm = this.value.toLowerCase(); |
| const rows = document.querySelectorAll('#contractorsTableBody tr'); |
| |
| rows.forEach(row => { |
| const text = row.textContent.toLowerCase(); |
| row.style.display = text.includes(searchTerm) ? '' : 'none'; |
| }); |
| }); |
| } |
| |
| function loadBanks() { |
| let html = ` |
| <div class="row mb-4"> |
| <div class="col-md-6"> |
| <h4>مدیریت بانکها و شعب</h4> |
| </div> |
| <div class="col-md-6 text-start"> |
| <button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#bankModal" onclick="resetBankForm()"> |
| <i class="bi bi-plus-circle me-2"></i> اضافه کردن بانک/شعبه جدید |
| </button> |
| </div> |
| </div> |
| |
| <div class="card"> |
| <div class="card-header"> |
| لیست بانکها و شعب |
| </div> |
| <div class="card-body"> |
| <div class="row mb-3"> |
| <div class="col-md-6"> |
| <div class="search-box"> |
| <i class="bi bi-search"></i> |
| <input type="text" class="form-control" id="searchBanks" placeholder="جستجوی بانک..."> |
| </div> |
| </div> |
| </div> |
| |
| <div class="table-responsive"> |
| <table class="table table-hover"> |
| <thead> |
| <tr> |
| <th>نام بانک</th> |
| <th>نام شعبه</th> |
| <th>کد شعبه</th> |
| <th>تلفن</th> |
| <th>عملیات</th> |
| </tr> |
| </thead> |
| <tbody id="banksTableBody"> |
| ${db.banks.map(b => ` |
| <tr> |
| <td>${b.name}</td> |
| <td>${b.branch}</td> |
| <td>${b.code || '-'}</td> |
| <td>${b.phone || '-'}</td> |
| <td> |
| <button class="btn btn-sm btn-outline-primary" onclick="editBank(${b.id})"> |
| <i class="bi bi-pencil"></i> |
| </button> |
| <button class="btn btn-sm btn-outline-danger" onclick="deleteBank(${b.id})"> |
| <i class="bi bi-trash"></i> |
| </button> |
| </td> |
| </tr> |
| `).join('')} |
| </tbody> |
| </table> |
| </div> |
| </div> |
| </div> |
| `; |
| |
| document.getElementById('pageContent').innerHTML = html; |
| |
| // Set up search functionality |
| document.getElementById('searchBanks').addEventListener('input', function() { |
| const searchTerm = this.value.toLowerCase(); |
| const rows = document.querySelectorAll('#banksTableBody tr'); |
| |
| rows.forEach(row => { |
| const text = row.textContent.toLowerCase(); |
| row.style.display = text.includes(searchTerm) ? '' : 'none'; |
| }); |
| }); |
| } |
| |
| function loadReports() { |
| let html = ` |
| <div class="row mb-4"> |
| <div class="col-md-12"> |
| <h4>گزارشها</h4> |
| </div> |
| </div> |
| |
| <div class="row mb-4"> |
| <div class="col-md-6"> |
| <div class="card"> |
| <div class="card-header"> |
| گزارش بر اساس وضعیت |
| </div> |
| <div class="card-body"> |
| <canvas id="reportStatusChart" width="400" height="300"></canvas> |
| </div> |
| </div> |
| </div> |
| <div class="col-md-6"> |
| <div class="card"> |
| <div class="card-header"> |
| گزارش بر اساس نوع |
| </div> |
| <div class="card-body"> |
| <canvas id="reportTypeChart" width="400" height="300"></canvas> |
| </div> |
| </div> |
| </div> |
| </div> |
| |
| <div class="card"> |
| <div class="card-header d-flex justify-content-between align-items-center"> |
| گزارش کامل ضمانتنامهها |
| <div> |
| <button class="btn btn-sm btn-outline-success" onclick="exportToExcel()"> |
| <i class="bi bi-file-earmark-excel me-2"></i> اکسل |
| </button> |
| <button class="btn btn-sm btn-outline-danger" onclick="exportToPDF()"> |
| <i class="bi bi-file-earmark-pdf me-2"></i> PDF |
| </button> |
| </div> |
| </div> |
| <div class="card-body"> |