| | <!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"> |