Spaces:
				
			
			
	
			
			
					
		Running
		
	
	
	
			
			
	
	
	
	
		
		
					
		Running
		
	| <html lang="ar" 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://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script> | |
| <style> | |
| :root { | |
| --primary-color: #25D366; | |
| --secondary-color: #128C7E; | |
| --accent-color: #34B7F1; | |
| --text-color: #333; | |
| --light-bg: #f5f5f5; | |
| --white: #ffffff; | |
| --error-color: #ff4444; | |
| --success-color: #00C851; | |
| --warning-color: #ffbb33; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; | |
| } | |
| body { | |
| background-color: var(--light-bg); | |
| color: var(--text-color); | |
| line-height: 1.6; | |
| } | |
| .container { | |
| max-width: 1000px; | |
| margin: 0 auto; | |
| padding: 20px; | |
| } | |
| header { | |
| background-color: var(--secondary-color); | |
| color: var(--white); | |
| padding: 20px 0; | |
| text-align: center; | |
| border-radius: 10px 10px 0 0; | |
| margin-bottom: 30px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| } | |
| header h1 { | |
| font-size: 28px; | |
| margin-bottom: 10px; | |
| } | |
| header p { | |
| font-size: 16px; | |
| opacity: 0.9; | |
| } | |
| .app-logo { | |
| font-size: 40px; | |
| margin-bottom: 15px; | |
| color: var(--white); | |
| } | |
| .main-container { | |
| background-color: var(--white); | |
| border-radius: 10px; | |
| padding: 30px; | |
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); | |
| } | |
| .upload-section { | |
| margin-bottom: 30px; | |
| border-bottom: 1px solid #eee; | |
| padding-bottom: 30px; | |
| } | |
| .upload-container { | |
| border: 2px dashed #ccc; | |
| padding: 30px; | |
| text-align: center; | |
| border-radius: 8px; | |
| transition: all 0.3s; | |
| background-color: var(--light-bg); | |
| position: relative; | |
| } | |
| .upload-container:hover { | |
| border-color: var(--accent-color); | |
| background-color: rgba(52, 183, 241, 0.05); | |
| } | |
| .upload-container.active { | |
| border-color: var(--primary-color); | |
| background-color: rgba(37, 211, 102, 0.05); | |
| } | |
| .upload-container.error { | |
| border-color: var(--error-color); | |
| background-color: rgba(255, 68, 68, 0.05); | |
| } | |
| .upload-container i { | |
| font-size: 48px; | |
| color: var(--accent-color); | |
| margin-bottom: 15px; | |
| } | |
| .upload-container p { | |
| margin-bottom: 20px; | |
| color: #666; | |
| } | |
| .btn { | |
| background-color: var(--primary-color); | |
| color: var(--white); | |
| border: none; | |
| padding: 12px 25px; | |
| border-radius: 50px; | |
| cursor: pointer; | |
| font-size: 16px; | |
| font-weight: 600; | |
| transition: all 0.3s; | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .btn:hover { | |
| background-color: var(--secondary-color); | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); | |
| } | |
| .btn i { | |
| margin-left: 8px; | |
| } | |
| .btn-secondary { | |
| background-color: #6c757d; | |
| } | |
| .btn-secondary:hover { | |
| background-color: #5a6268; | |
| } | |
| .form-group { | |
| margin-bottom: 25px; | |
| } | |
| .form-group label { | |
| display: block; | |
| margin-bottom: 8px; | |
| font-weight: 600; | |
| color: var(--text-color); | |
| } | |
| .form-control { | |
| width: 100%; | |
| padding: 12px 15px; | |
| border: 1px solid #ddd; | |
| border-radius: 6px; | |
| font-size: 16px; | |
| transition: border 0.3s; | |
| } | |
| .form-control:focus { | |
| border-color: var(--accent-color); | |
| outline: none; | |
| box-shadow: 0 0 0 3px rgba(52, 183, 241, 0.2); | |
| } | |
| textarea.form-control { | |
| min-height: 120px; | |
| resize: vertical; | |
| } | |
| .preview-section { | |
| background-color: var(--light-bg); | |
| padding: 20px; | |
| border-radius: 8px; | |
| margin-top: 30px; | |
| border: 1px solid #eee; | |
| } | |
| .preview-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 15px; | |
| } | |
| .preview-content { | |
| background-color: var(--white); | |
| padding: 15px; | |
| border-radius: 8px; | |
| border: 1px solid #ddd; | |
| max-height: 300px; | |
| overflow-y: auto; | |
| } | |
| .student-list { | |
| list-style: none; | |
| } | |
| .student-item { | |
| padding: 12px 15px; | |
| border-bottom: 1px solid #eee; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .student-item:last-child { | |
| border-bottom: none; | |
| } | |
| .send-section { | |
| margin-top: 40px; | |
| text-align: center; | |
| } | |
| .send-btn { | |
| padding: 15px 40px; | |
| font-size: 18px; | |
| } | |
| /* File Requirements Info */ | |
| .file-requirements { | |
| background-color: rgba(52, 183, 241, 0.1); | |
| border-left: 4px solid var(--accent-color); | |
| padding: 12px 15px; | |
| margin-top: 15px; | |
| border-radius: 4px; | |
| font-size: 14px; | |
| } | |
| .file-requirements h4 { | |
| margin-bottom: 8px; | |
| color: var(--secondary-color); | |
| } | |
| .file-requirements ul { | |
| list-style-position: inside; | |
| padding-right: 10px; | |
| } | |
| .file-requirements li { | |
| margin-bottom: 5px; | |
| } | |
| /* Loading Animation */ | |
| .spinner { | |
| width: 24px; | |
| height: 24px; | |
| border: 3px solid rgba(255,255,255,0.3); | |
| border-radius: 50%; | |
| border-top-color: var(--white); | |
| animation: spin 1s ease-in-out infinite; | |
| display: inline-block; | |
| vertical-align: middle; | |
| margin-left: 8px; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| /* Modals */ | |
| .modal { | |
| display: none; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: rgba(0, 0, 0, 0.5); | |
| z-index: 1000; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .modal-content { | |
| background-color: var(--white); | |
| padding: 30px; | |
| border-radius: 10px; | |
| width: 90%; | |
| max-width: 500px; | |
| max-height: 80vh; | |
| overflow-y: auto; | |
| } | |
| .modal-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 20px; | |
| } | |
| .modal-title { | |
| font-size: 20px; | |
| font-weight: 600; | |
| } | |
| .close-modal { | |
| background: none; | |
| border: none; | |
| font-size: 24px; | |
| cursor: pointer; | |
| color: #666; | |
| } | |
| .progress-container { | |
| margin: 20px 0; | |
| } | |
| .progress-bar { | |
| height: 10px; | |
| background-color: #eee; | |
| border-radius: 5px; | |
| overflow: hidden; | |
| } | |
| .progress { | |
| height: 100%; | |
| background-color: var(--primary-color); | |
| width: 0; | |
| transition: width 0.3s; | |
| } | |
| .status-message { | |
| text-align: center; | |
| margin: 15px 0; | |
| font-size: 14px; | |
| color: #666; | |
| } | |
| .error { | |
| color: var(--error-color); | |
| } | |
| .success { | |
| color: var(--success-color); | |
| } | |
| .warning { | |
| color: var(--warning-color); | |
| } | |
| .template-tags { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 8px; | |
| margin-top: 10px; | |
| } | |
| .tag { | |
| background-color: var(--light-bg); | |
| padding: 5px 10px; | |
| border-radius: 4px; | |
| font-size: 14px; | |
| cursor: pointer; | |
| border: 1px solid #ddd; | |
| transition: all 0.2s; | |
| } | |
| .tag:hover { | |
| background-color: var(--accent-color); | |
| color: var(--white); | |
| } | |
| /* Responsive */ | |
| @media (max-width: 768px) { | |
| .container { | |
| padding: 15px; | |
| } | |
| .main-container { | |
| padding: 20px; | |
| } | |
| .upload-container { | |
| padding: 20px 15px; | |
| } | |
| .btn { | |
| padding: 10px 20px; | |
| font-size: 14px; | |
| } | |
| .student-item { | |
| flex-direction: column; | |
| align-items: flex-start; | |
| } | |
| .student-item div:nth-child(2) { | |
| margin-top: 8px; | |
| max-width: 100%; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="container"> | |
| <div class="app-logo"> | |
| <i class="fab fa-whatsapp"></i> | |
| </div> | |
| <h1>نظام إرسال رسائل الواتساب للطلاب</h1> | |
| <p>أرسل رسائل مخصصة للطلاب بسهولة وبشكل آلي</p> | |
| </div> | |
| </header> | |
| <div class="container"> | |
| <div class="main-container"> | |
| <section class="upload-section"> | |
| <h2><i class="fas fa-file-upload"></i> تحميل ملف الطلاب</h2> | |
| <div class="upload-container" id="uploadContainer"> | |
| <i class="fas fa-file-excel"></i> | |
| <p>قم بسحب وإسقاط ملف Excel هنا أو انقر لاختيار الملف</p> | |
| <input type="file" id="fileInput" accept=".xlsx,.xls,.csv" style="display: none;"> | |
| <button class="btn" id="selectFileBtn"> | |
| <i class="fas fa-folder-open"></i> اختر ملف | |
| </button> | |
| </div> | |
| <div class="file-requirements"> | |
| <h4><i class="fas fa-info-circle"></i> متطلبات الملف:</h4> | |
| <ul> | |
| <li>يجب أن يكون الملف من نوع Excel (xlsx, xls) أو CSV</li> | |
| <li>يجب أن يحتوي على عمود باسم "الاسم" أو "اسم الطالب"</li> | |
| <li>يجب أن يحتوي على عمود باسم "رقم الجوال" أو "الهاتف"</li> | |
| <li>يمكنك <a href="#" id="downloadSample">تحميل نموذج</a> لاستخدامه كمرجع</li> | |
| </ul> | |
| </div> | |
| <div id="fileInfo" style="margin-top: 15px; display: none;"> | |
| <div class="file-details" style="display: flex; align-items: center;"> | |
| <i class="fas fa-file-excel" style="color: var(--success-color); font-size: 24px; margin-left: 10px;"></i> | |
| <div> | |
| <p style="font-weight: 600; margin-bottom: 5px;" id="fileName"></p> | |
| <p style="color: #666; font-size: 14px;" id="fileSize"></p> | |
| <p style="color: #666; font-size: 14px;" id="studentCount"></p> | |
| </div> | |
| <button id="clearFileBtn" style="background: none; border: none; margin-right: auto; color: var(--error-color); cursor: pointer;"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="message-section"> | |
| <h2><i class="fas fa-comment-alt"></i> تكوين الرسالة</h2> | |
| <div class="form-group"> | |
| <label for="messageTemplate">نموذج الرسالة</label> | |
| <textarea id="messageTemplate" class="form-control" placeholder="عزيزي {اسم الطالب}، نود إعلامك بأن... الرابط: {الرابط}"></textarea> | |
| <div class="template-tags"> | |
| <span class="tag" data-tag="{اسم الطالب}">{اسم الطالب}</span> | |
| <span class="tag" data-tag="{الرابط}">{الرابط}</span> | |
| <span class="tag" data-tag="{الرقم}">{الرقم}</span> | |
| <span class="tag" data-tag="{التاريخ}">{التاريخ}</span> | |
| </div> | |
| </div> | |
| <div class="form-group"> | |
| <label for="customLink">رابط مخصص (اختياري)</label> | |
| <input type="url" id="customLink" class="form-control" placeholder="https://example.com"> | |
| </div> | |
| <div class="preview-section"> | |
| <div class="preview-header"> | |
| <h3><i class="fas fa-eye"></i> معاينة الرسائل</h3> | |
| <span id="previewCount" style="font-size: 14px; color: #666;"></span> | |
| </div> | |
| <div class="preview-content"> | |
| <p id="previewMessage">سيتم عرض معاينة الرسائل هنا بعد تحميل الملف وإدخال نموذج الرسالة.</p> | |
| <ul class="student-list" id="studentList" style="display: none;"></ul> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="send-section"> | |
| <button class="btn send-btn" id="sendMessagesBtn" disabled> | |
| <i class="fab fa-whatsapp"></i> إرسال الرسائل | |
| </button> | |
| <p id="sendInfo" style="margin-top: 10px; font-size: 14px; color: #666;"></p> | |
| </section> | |
| </div> | |
| </div> | |
| <!-- Progress Modal --> | |
| <div class="modal" id="progressModal"> | |
| <div class="modal-content"> | |
| <div class="modal-header"> | |
| <h3 class="modal-title"><i class="fas fa-paper-plane"></i> جارٍ إرسال الرسائل</h3> | |
| <button class="close-modal" id="closeModalBtn">×</button> | |
| </div> | |
| <div class="progress-container"> | |
| <div class="progress-bar"> | |
| <div class="progress" id="progressBar"></div> | |
| </div> | |
| <div class="status-message" id="statusMessage"> | |
| جاهز للبدء... | |
| </div> | |
| </div> | |
| <div id="successCount" style="text-align: center; margin: 10px 0; color: var(--success-color);"></div> | |
| <div id="errorCount" style="text-align: center; margin: 10px 0; color: var(--error-color);"></div> | |
| <div style="text-align: center;"> | |
| <button class="btn btn-secondary" id="cancelSendingBtn"> | |
| <i class="fas fa-stop"></i> إلغاء العملية | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Success Modal --> | |
| <div class="modal" id="successModal"> | |
| <div class="modal-content"> | |
| <div style="text-align: center; margin: 20px 0;"> | |
| <i class="fas fa-check-circle" style="font-size: 60px; color: var(--success-color);"></i> | |
| <h3 style="margin-top: 15px;">تم إرسال الرسائل بنجاح!</h3> | |
| </div> | |
| <div style="margin: 20px 0; text-align: center;"> | |
| <p id="successMessage"></p> | |
| <div id="finalStats" style="margin-top: 15px;"></div> | |
| </div> | |
| <div style="text-align: center;"> | |
| <button class="btn" id="okSuccessBtn"> | |
| <i class="fas fa-check"></i> حسناً | |
| </button> | |
| <button class="btn btn-secondary" id="downloadReportBtn" style="margin-right: 10px;"> | |
| <i class="fas fa-download"></i> تنزيل التقرير | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Error Modal --> | |
| <div class="modal" id="errorModal"> | |
| <div class="modal-content"> | |
| <div style="text-align: center; margin: 20px 0;"> | |
| <i class="fas fa-exclamation-circle" style="font-size: 60px; color: var(--error-color);"></i> | |
| <h3 style="margin-top: 15px;" id="errorModalTitle">حدث خطأ أثناء معالجة الملف</h3> | |
| </div> | |
| <div style="margin: 20px 0; text-align: center;"> | |
| <p id="errorModalMessage"></p> | |
| </div> | |
| <div style="text-align: center;"> | |
| <button class="btn" id="okErrorBtn"> | |
| <i class="fas fa-check"></i> حسناً | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // DOM Elements | |
| const uploadContainer = document.getElementById('uploadContainer'); | |
| const fileInput = document.getElementById('fileInput'); | |
| const selectFileBtn = document.getElementById('selectFileBtn'); | |
| const fileInfo = document.getElementById('fileInfo'); | |
| const fileName = document.getElementById('fileName'); | |
| const fileSize = document.getElementById('fileSize'); | |
| const studentCount = document.getElementById('studentCount'); | |
| const clearFileBtn = document.getElementById('clearFileBtn'); | |
| const messageTemplate = document.getElementById('messageTemplate'); | |
| const customLink = document.getElementById('customLink'); | |
| const previewMessage = document.getElementById('previewMessage'); | |
| const previewCount = document.getElementById('previewCount'); | |
| const studentList = document.getElementById('studentList'); | |
| const sendMessagesBtn = document.getElementById('sendMessagesBtn'); | |
| const sendInfo = document.getElementById('sendInfo'); | |
| const progressModal = document.getElementById('progressModal'); | |
| const progressBar = document.getElementById('progressBar'); | |
| const statusMessage = document.getElementById('statusMessage'); | |
| const successCount = document.getElementById('successCount'); | |
| const errorCount = document.getElementById('errorCount'); | |
| const closeModalBtn = document.getElementById('closeModalBtn'); | |
| const cancelSendingBtn = document.getElementById('cancelSendingBtn'); | |
| const successModal = document.getElementById('successModal'); | |
| const successMessage = document.getElementById('successMessage'); | |
| const finalStats = document.getElementById('finalStats'); | |
| const okSuccessBtn = document.getElementById('okSuccessBtn'); | |
| const downloadReportBtn = document.getElementById('downloadReportBtn'); | |
| const errorModal = document.getElementById('errorModal'); | |
| const errorModalTitle = document.getElementById('errorModalTitle'); | |
| const errorModalMessage = document.getElementById('errorModalMessage'); | |
| const okErrorBtn = document.getElementById('okErrorBtn'); | |
| const downloadSampleLink = document.getElementById('downloadSample'); | |
| // Variables | |
| let studentsData = []; | |
| let sendingProcess = null; | |
| let successCounter = 0; | |
| let errorCounter = 0; | |
| // Event Listeners | |
| selectFileBtn.addEventListener('click', function() { | |
| fileInput.click(); | |
| }); | |
| uploadContainer.addEventListener('dragover', function(e) { | |
| e.preventDefault(); | |
| uploadContainer.classList.add('active'); | |
| }); | |
| uploadContainer.addEventListener('dragleave', function() { | |
| uploadContainer.classList.remove('active'); | |
| }); | |
| uploadContainer.addEventListener('drop', function(e) { | |
| e.preventDefault(); | |
| uploadContainer.classList.remove('active'); | |
| if (e.dataTransfer.files.length) { | |
| fileInput.files = e.dataTransfer.files; | |
| handleFileUpload(); | |
| } | |
| }); | |
| fileInput.addEventListener('change', handleFileUpload); | |
| clearFileBtn.addEventListener('click', function() { | |
| resetFileUpload(); | |
| }); | |
| messageTemplate.addEventListener('input', function() { | |
| updatePreview(); | |
| updateSendButtonState(); | |
| }); | |
| customLink.addEventListener('input', function() { | |
| updatePreview(); | |
| }); | |
| sendMessagesBtn.addEventListener('click', startSendingProcess); | |
| closeModalBtn.addEventListener('click', function() { | |
| progressModal.style.display = 'none'; | |
| }); | |
| cancelSendingBtn.addEventListener('click', function() { | |
| if (sendingProcess) { | |
| clearInterval(sendingProcess); | |
| sendingProcess = null; | |
| statusMessage.innerHTML = '<span class="warning">تم إيقاف عملية الإرسال</span>'; | |
| cancelSendingBtn.disabled = true; | |
| // Show final stats | |
| showFinalStats(); | |
| } | |
| }); | |
| okSuccessBtn.addEventListener('click', function() { | |
| successModal.style.display = 'none'; | |
| }); | |
| okErrorBtn.addEventListener('click', function() { | |
| errorModal.style.display = 'none'; | |
| }); | |
| downloadReportBtn.addEventListener('click', function() { | |
| downloadReport(); | |
| }); | |
| downloadSampleLink.addEventListener('click', function(e) { | |
| e.preventDefault(); | |
| downloadSampleFile(); | |
| }); | |
| // Add event listeners for template tags | |
| document.querySelectorAll('.tag').forEach(tag => { | |
| tag.addEventListener('click', function() { | |
| const tagValue = this.getAttribute('data-tag'); | |
| insertAtCursor(messageTemplate, tagValue); | |
| messageTemplate.focus(); | |
| }); | |
| }); | |
| // Functions | |
| function handleFileUpload() { | |
| const file = fileInput.files[0]; | |
| if (!file) return; | |
| // Check file type | |
| const validTypes = ['application/vnd.ms-excel', | |
| 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', | |
| 'text/csv']; | |
| if (!validTypes.includes(file.type) && | |
| !file.name.endsWith('.xls') && | |
| !file.name.endsWith('.xlsx') && | |
| !file.name.endsWith('.csv')) { | |
| showError('نوع ملف غير مدعوم', 'الرجاء تحميل ملف Excel (xls, xlsx) أو ملف CSV.'); | |
| resetFileUpload(); | |
| return; | |
| } | |
| // Show loading state | |
| uploadContainer.classList.add('active'); | |
| selectFileBtn.innerHTML = '<span class="spinner"></span> جاري معالجة الملف...'; | |
| selectFileBtn.disabled = true; | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| try { | |
| const data = new Uint8Array(e.target.result); | |
| const workbook = XLSX.read(data, { type: 'array' }); | |
| // Get first sheet | |
| const firstSheet = workbook.Sheets[workbook.SheetNames[0]]; | |
| // Convert to JSON | |
| const jsonData = XLSX.utils.sheet_to_json(firstSheet); | |
| if (jsonData.length === 0) { | |
| showError('ملف فارغ', 'الملف الذي تم تحميله لا يحتوي على أي بيانات.'); | |
| resetFileUpload(); | |
| return; | |
| } | |
| // Process data and look for required columns | |
| let nameKey = findColumnKey(jsonData[0], ['اسم', 'اسم الطالب', 'الاسم', 'الطالب', 'name', 'student']); | |
| let phoneKey = findColumnKey(jsonData[0], ['رقم الجوال', 'الهاتف', 'رقم الهاتف', 'phone', 'mobile', 'whatsapp']); | |
| if (!nameKey || !phoneKey) { | |
| showError('تنسيق ملف غير صحيح', 'الملف يجب أن يحتوي على أعمدة لكل من الاسم ورقم الجوال.'); | |
| resetFileUpload(); | |
| return; | |
| } | |
| studentsData = jsonData.map(row => { | |
| return { | |
| name: row[nameKey] || 'غير معروف', | |
| phone: sanitizePhoneNumber(row[phoneKey]) | |
| }; | |
| }).filter(student => student.phone); // Filter out invalid phone numbers | |
| if (studentsData.length === 0) { | |
| showError('بيانات غير صالحة', 'لم يتم العثور على أرقام هواتف صالحة في الملف.'); | |
| resetFileUpload(); | |
| return; | |
| } | |
| // Show file info | |
| fileName.textContent = file.name; | |
| fileSize.textContent = formatFileSize(file.size); | |
| studentCount.textContent = `عدد الطلاب: ${studentsData.length}`; | |
| fileInfo.style.display = 'block'; | |
| // Update UI | |
| uploadContainer.classList.remove('active'); | |
| selectFileBtn.innerHTML = '<i class="fas fa-folder-open"></i> اختر ملف'; | |
| selectFileBtn.disabled = false; | |
| updatePreview(); | |
| updateSendButtonState(); | |
| updateSendInfo(); | |
| } catch (error) { | |
| console.error('Error processing file:', error); | |
| showError('خطأ في معالجة الملف', 'حدث خطأ أثناء قراءة الملف. الرجاء التحقق من صحة الملف وحاول مرة أخرى.'); | |
| resetFileUpload(); | |
| } | |
| }; | |
| reader.onerror = function() { | |
| showError('خطأ في قراءة الملف', 'تعذر قراءة الملف. الرجاء المحاولة مرة أخرى.'); | |
| resetFileUpload(); | |
| }; | |
| reader.readAsArrayBuffer(file); | |
| } | |
| function findColumnKey(row, possibleKeys) { | |
| const existingKeys = Object.keys(row); | |
| for (let key of possibleKeys) { | |
| if (existingKeys.includes(key)) { | |
| return key; | |
| } | |
| } | |
| return null; | |
| } | |
| function sanitizePhoneNumber(phone) { | |
| if (!phone) return null; | |
| // Convert to string if it's a number | |
| phone = phone.toString(); | |
| // Remove all non-digit characters | |
| phone = phone.replace(/\D/g, ''); | |
| // Saudi number validation (966 + 9 digits) | |
| if (phone.startsWith('966') && phone.length === 12) { | |
| return phone; | |
| } | |
| // International format | |
| if (phone.length >= 10 && phone.length <= 15) { | |
| return phone; | |
| } | |
| return null; | |
| } | |
| function formatFileSize(bytes) { | |
| if (bytes === 0) return '0 بايت'; | |
| const k = 1024; | |
| const sizes = ['بايت', 'كيلوبايت', 'ميجابايت', 'جيجابايت']; | |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); | |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; | |
| } | |
| function resetFileUpload() { | |
| fileInput.value = ''; | |
| fileInfo.style.display = 'none'; | |
| studentsData = []; | |
| uploadContainer.classList.remove('active', 'error'); | |
| selectFileBtn.innerHTML = '<i class="fas fa-folder-open"></i> اختر ملف'; | |
| selectFileBtn.disabled = false; | |
| updatePreview(); | |
| updateSendButtonState(); | |
| updateSendInfo(); | |
| } | |
| function updatePreview() { | |
| if (studentsData.length === 0) { | |
| studentList.style.display = 'none'; | |
| previewMessage.style.display = 'block'; | |
| previewMessage.textContent = 'سيتم عرض معاينة الرسائل هنا بعد تحميل الملف وإدخال نموذج الرسالة.'; | |
| previewCount.textContent = ''; | |
| return; | |
| } | |
| let message = messageTemplate.value; | |
| if (!message) { | |
| studentList.style.display = 'none'; | |
| previewMessage.style.display = 'block'; | |
| previewMessage.textContent = 'الرجاء إدخال نموذج الرسالة لمعاينة الرسائل.'; | |
| previewCount.textContent = ''; | |
| return; | |
| } | |
| studentList.innerHTML = ''; | |
| previewMessage.style.display = 'none'; | |
| studentList.style.display = 'block'; | |
| previewCount.textContent = `عرض ${Math.min(5, studentsData.length)} من ${studentsData.length}`; | |
| // Show preview for first 5 students | |
| const previewCountNum = Math.min(5, studentsData.length); | |
| for (let i = 0; i < previewCountNum; i++) { | |
| const student = studentsData[i]; | |
| const link = customLink.value || '{الرابط}'; | |
| const processedMessage = processMessage(message, student.name, link); | |
| const li = document.createElement('li'); | |
| li.className = 'student-item'; | |
| li.innerHTML = ` | |
| <div style="flex: 1;"> | |
| <strong>${student.name}</strong> | |
| <div style="font-size: 12px; color: #666; margin-top: 4px;">${formatPhoneNumber(student.phone)}</div> | |
| </div> | |
| <div style="max-width: 60%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"> | |
| ${processedMessage} | |
| </div> | |
| `; | |
| studentList.appendChild(li); | |
| } | |
| if (studentsData.length > 5) { | |
| const li = document.createElement('li'); | |
| li.className = 'student-item'; | |
| li.style.textAlign = 'center'; | |
| li.style.color = '#666'; | |
| li.textContent = `و ${studentsData.length - 5} طلاب آخرين...`; | |
| studentList.appendChild(li); | |
| } | |
| } | |
| function formatPhoneNumber(phone) { | |
| // Format Saudi numbers | |
| if (phone.startsWith('966')) { | |
| return `+${phone.substring(0, 3)} ${phone.substring(3, 5)} ${phone.substring(5, 8)} ${phone.substring(8)}`; | |
| } | |
| return `+${phone}`; | |
| } | |
| function processMessage(template, name, link) { | |
| const today = new Date(); | |
| const dateStr = `${today.getDate()}/${today.getMonth() + 1}/${today.getFullYear()}`; | |
| return template | |
| .replace(/{اسم الطالب}/g, name) | |
| .replace(/{الرابط}/g, link) | |
| .replace(/{الرقم}/g, '+966XXXXXXXXX') | |
| .replace(/{التاريخ}/g, dateStr); | |
| } | |
| function updateSendButtonState() { | |
| sendMessagesBtn.disabled = studentsData.length === 0 || !messageTemplate.value; | |
| } | |
| function updateSendInfo() { | |
| if (studentsData.length > 0) { | |
| sendInfo.textContent = `جاهز لإرسال ${studentsData.length} رسالة`; | |
| } else { | |
| sendInfo.textContent = ''; | |
| } | |
| } | |
| function insertAtCursor(textarea, text) { | |
| const startPos = textarea.selectionStart; | |
| const endPos = textarea.selectionEnd; | |
| const beforeText = textarea.value.substring(0, startPos); | |
| const afterText = textarea.value.substring(endPos, textarea.value.length); | |
| textarea.value = beforeText + text + afterText; | |
| textarea.selectionStart = startPos + text.length; | |
| textarea.selectionEnd = startPos + text.length; | |
| textarea.focus(); | |
| // Trigger input event to update preview | |
| const event = new Event('input', { bubbles: true }); | |
| textarea.dispatchEvent(event); | |
| } | |
| function startSendingProcess() { | |
| if (studentsData.length === 0 || !messageTemplate.value) return; | |
| // Reset counters | |
| successCounter = 0; | |
| errorCounter = 0; | |
| // Show progress modal | |
| progressModal.style.display = 'flex'; | |
| progressBar.style.width = '0%'; | |
| successCount.textContent = ''; | |
| errorCount.textContent = ''; | |
| cancelSendingBtn.disabled = false; | |
| const totalStudents = studentsData.length; | |
| const link = customLink.value || '{الرابط}'; | |
| let currentIndex = 0; | |
| sendingProcess = setInterval(() => { | |
| if (currentIndex >= totalStudents) { | |
| // Finished sending | |
| clearInterval(sendingProcess); | |
| sendingProcess = null; | |
| // Show success message | |
| progressModal.style.display = 'none'; | |
| if (errorCounter === 0) { | |
| successMessage.textContent = `تم إرسال جميع الرسائل بنجاح إلى ${totalStudents} طالب!`; | |
| } else { | |
| successMessage.textContent = `تم إرسال ${successCounter} رسالة بنجاح من أصل ${totalStudents}.`; | |
| } | |
| showFinalStats(); | |
| successModal.style.display = 'flex'; | |
| return; | |
| } | |
| const student = studentsData[currentIndex]; | |
| const message = processMessage(messageTemplate.value, student.name, link); | |
| // Update progress | |
| const percent = Math.round(((currentIndex + 1) / totalStudents) * 100); | |
| progressBar.style.width = `${percent}%`; | |
| statusMessage.innerHTML = `جاري إرسال الرسالة إلى <strong>${student.name}</strong> (${currentIndex + 1}/${totalStudents})...`; | |
| // Simulate sending (in a real app, you would call WhatsApp API) | |
| setTimeout(() => { | |
| // Randomly simulate failures (for demo purposes) | |
| if (Math.random() < 0.1) { | |
| errorCounter++; | |
| errorCount.textContent = `رسائل غير مرسلة: ${errorCounter}`; | |
| // Retry logic (for demo, just continue) | |
| if (Math.random() < 0.5 && currentIndex < totalStudents) { | |
| statusMessage.innerHTML = `<span class="error">فشل إرسال الرسالة إلى ${student.name} - جاري إعادة المحاولة...</span>`; | |
| currentIndex--; | |
| } else { | |
| statusMessage.innerHTML = `<span class="error">فشل إرسال الرسالة إلى ${student.name} - سيتم تخطي هذا الطالب</span>`; | |
| } | |
| } else { | |
| successCounter++; | |
| successCount.textContent = `رسائل مرسلة: ${successCounter}`; | |
| } | |
| currentIndex++; | |
| }, 1000); | |
| }, 1500); | |
| } | |
| function showFinalStats() { | |
| const total = successCounter + errorCounter; | |
| finalStats.innerHTML = ` | |
| <div style="display: flex; justify-content: space-around; margin-top: 10px;"> | |
| <div> | |
| <div style="font-size: 24px; color: var(--success-color);">${successCounter}</div> | |
| <div style="font-size: 14px;">ناجحة</div> | |
| </div> | |
| <div> | |
| <div style="font-size: 24px; color: var(--error-color);">${errorCounter}</div> | |
| <div style="font-size: 14px;">فاشلة</div> | |
| </div> | |
| <div> | |
| <div style="font-size: 24px; color: var(--secondary-color);">${total}</div> | |
| <div style="font-size: 14px;">المجموع</div> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| function downloadReport() { | |
| // Create a CSV report | |
| let csvContent = "اسم الطالب,رقم الجوال,الحالة\n"; | |
| studentsData.forEach((student, index) => { | |
| const status = index < successCounter ? "ناجح" : "فشل"; | |
| csvContent += `${student.name},${formatPhoneNumber(student.phone)},${status}\n`; | |
| }); | |
| // Create download link | |
| const blob = new Blob(["\uFEFF"+csvContent], { type: 'text/csv;charset=utf-8;' }); | |
| const url = URL.createObjectURL(blob); | |
| const link = document.createElement('a'); | |
| link.setAttribute('href', url); | |
| link.setAttribute('download', `تقرير_إرسال_الرسائل_${new Date().toLocaleDateString('ar')}.csv`); | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| } | |
| function downloadSampleFile() { | |
| // Create sample Excel file data | |
| const sampleData = [ | |
| ['اسم الطالب', 'رقم الجوال', 'البريد الإلكتروني'], | |
| ['أحمد محمد', '966501234567', 'ahmed@example.com'], | |
| ['فاطمة علي', '966502345678', 'fatima@example.com'], | |
| ['محمد حسن', '966503456789', 'mohamed@example.com'] | |
| ]; | |
| // Create workbook | |
| const wb = XLSX.utils.book_new(); | |
| const ws = XLSX.utils.aoa_to_sheet(sampleData); | |
| XLSX.utils.book_append_sheet(wb, ws, "الطلاب"); | |
| // Generate and download | |
| XLSX.writeFile(wb, 'نموذج_قائمة_الطلاب.xlsx'); | |
| } | |
| function showError(title, message) { | |
| uploadContainer.classList.add('error'); | |
| errorModalTitle.textContent = title; | |
| errorModalMessage.textContent = message; | |
| errorModal.style.display = 'flex'; | |
| } | |
| // Handle clicks outside modal to close it | |
| window.addEventListener('click', function(event) { | |
| if (event.target === progressModal) { | |
| progressModal.style.display = 'none'; | |
| } | |
| if (event.target === successModal) { | |
| successModal.style.display = 'none'; | |
| } | |
| if (event.target === errorModal) { | |
| errorModal.style.display = 'none'; | |
| } | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |