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