📎 أفلت الملف في المكان المناسب

📋 الجهات الداخلية

اختر الموظف

الإجمالي

0

🔶 قيد الإجراء

0

للإفادة / لإبداء الرأي

0

لاتخاذ ما يلزم

0

مكتملة

0

📊 التقارير

0

🔴 عاجلة

0

➕ إضافة مذكرة جديدة

الأولوية:
🔶 ملفات قيد الإجراء
✅ الملف النهائي PDF
#المذكرةالجهةرقم الخطاب/المذكرةنوع المذكرة الموضوع والملفاتالتوجيهتفاصيل للإفادة/لإبداء الرأيالملف النهائي
جاري التحميل...

📦 الأرشيف 0

ℹ️ يتم نقل المذكرات المكتملة (التي لها ملف نهائي PDF وأكملت 30 يوم) تلقائياً للأرشيف لتسريع النظام. يمكنك استعراضها هنا أو إعادتها للنظام.
#المذكرةالجهةالموضوع التوجيهالملف النهائيتاريخ الأرشفة
📅 ${dayName} ${fullDate}
🕐 ${timeStr}

📋 التقرير اليومي للمذكرات

قسم الجهات الداخلية ─ وزارة التربية والتعليم
${stats.total}
📊 المجموع
${stats.memos}
📋 المذكرات
${stats.letters}
📨 الخطابات
${stats.urgent}
🔴 العاجل
${todayMemos.length === 0 ? `
📭

لم تتم إضافة أي مذكرات اليوم

سيظهر التقرير عند إضافة أو استيراد مذكرات جديدة

` : `
📋 تفاصيل المذكرات المُضافة اليوم
${stats.total} مذكرة
المحدد: 0 من ${stats.total}
${todayMemos.map((m, idx) => { const isUrgent = m.priority === 'urgent' && !m.finalFile; const isReport = m.isReport; const rowClass = isUrgent ? 'urgent-row' : (isReport ? 'report-row' : ''); const hasFiles = (m.progressFiles && m.progressFiles.length > 0) || (m.replyFiles && m.replyFiles.length > 0) || m.finalFile; const fileCount = (m.progressFiles ? m.progressFiles.length : 0) + (m.replyFiles ? m.replyFiles.length : 0) + (m.finalFile ? 1 : 0); const owner = m.owner || currentEmployee || '—'; const ownerShort = owner.split(' ')[0]; return ``; }).join('')}
م الرقم الموضوع الجهة الكاتب الملفات
${idx + 1} ${escape(m.memoNumber || '—')} ${isUrgent ? '
🔴 عاجل
' : ''} ${isReport ? '
📊 تقرير
' : ''} ${m.hasLetter ? `
📨 خطاب${m.letterNum ? ': ' + escape(m.letterNum) : ''}${m.letterApproved ? ' ✅' : ' ⏳'}
` : ''}
${escape(m.subject || '—')} ${m.letterNumber ? `
📄 ${escape(m.letterNumber)}
` : ''}
${escape(m.sender || '—')} ${escape(ownerShort)} ${hasFiles ? `✓ ${fileCount} ملف` : '⚠️ لا يوجد'}
`} ${stats.withoutFiles > 0 ? `

⚠️ مذكرات تحتاج رفع ملفات (${stats.withoutFiles})

` : ''} ${stats.urgent > 0 ? `

🔴 المذكرات العاجلة (${stats.urgent})

` : ''}
`; // فتح في نافذة جديدة const reportWindow = window.open('', '_blank', 'width=1000,height=800,scrollbars=yes'); if (!reportWindow) { alert('⚠️ يرجى السماح للنوافذ المنبثقة (Pop-ups) لعرض التقرير'); return; } reportWindow.document.write(reportHTML); reportWindow.document.close(); showToast(`📋 التقرير اليومي جاهز (${stats.total} مذكرة)`, 'success', 3000); } // ===== التقرير الشهري الشامل ===== function generateMonthlyReport() { // اختيار الشهر (افتراضي: الشهر الحالي) const now = new Date(); const currentMonth = now.getMonth(); const currentYear = now.getFullYear(); // أول وآخر يوم في الشهر const monthStart = new Date(currentYear, currentMonth, 1); monthStart.setHours(0, 0, 0, 0); const monthEnd = new Date(currentYear, currentMonth + 1, 1); monthEnd.setHours(0, 0, 0, 0); // المعاملات هذا الشهر const monthMemos = memos.filter(m => { if (!m.createdAt) return false; const created = new Date(m.createdAt); return created >= monthStart && created < monthEnd; }); if (monthMemos.length === 0) { if (!confirm(`ℹ️ لم تتم إضافة أي مذكرات هذا الشهر.\nهل تريد عرض التقرير فارغاً؟`)) { return; } } // ترتيب حسب التاريخ monthMemos.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt)); // إحصائيات الشهر const stats = { total: monthMemos.length, memos: monthMemos.filter(m => !m.hasLetter).length, letters: monthMemos.filter(m => m.hasLetter).length, urgent: monthMemos.filter(m => m.priority === 'urgent').length, reports: monthMemos.filter(m => m.isReport).length, completed: monthMemos.filter(m => m.finalFile).length, progress: monthMemos.filter(m => m.direction === 'قيد الإجراء' && !m.finalFile).length, info: monthMemos.filter(m => m.direction === 'للإفادة / لإبداء الرأي').length, action: monthMemos.filter(m => m.direction === 'لاتخاذ ما يلزم').length }; // معدل الإنجاز const completionRate = stats.total > 0 ? Math.round((stats.completed / stats.total) * 100) : 0; // اسم الشهر بالعربي const monthNames = ['يناير', 'فبراير', 'مارس', 'إبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'أكتوبر', 'نوفمبر', 'ديسمبر']; const monthName = monthNames[currentMonth]; // بناء HTML const reportHTML = ` التقرير الشهري - ${monthName} ${currentYear}

📊 التقرير الشهري

قسم الجهات الداخلية ─ وزارة التربية والتعليم
📅 ${monthName} ${currentYear}
${stats.total}
📊 المجموع
${stats.memos}
📋 المذكرات
${stats.letters}
📨 الخطابات
${stats.urgent}
🔴 العاجل
${monthMemos.length === 0 ? `
📭

لم تتم إضافة أي معاملات هذا الشهر

` : `
📋 تفاصيل المعاملات الشهرية
المحدد: 0 من ${stats.total}
${monthMemos.map((m, idx) => { const isUrgent = m.priority === 'urgent' && !m.finalFile; const isReport = m.isReport; const hasLetter = m.hasLetter; const rowClass = isUrgent ? 'urgent-row' : (hasLetter ? 'letter-row' : (isReport ? 'report-row' : '')); const dateStr = m.createdAt ? new Date(m.createdAt).toLocaleDateString('ar-EG', { day:'2-digit', month:'2-digit' }) : '—'; return ``; }).join('')}
م الرقم التاريخ الموضوع الجهة الحالة
${idx + 1} ${escape(m.memoNumber || '—')} ${isUrgent ? '
🔴
' : ''} ${isReport ? '
📊
' : ''} ${hasLetter ? `
📨${m.letterNum ? ' ' + escape(m.letterNum) : ''}${m.letterApproved ? ' ✅' : ' ⏳'}
` : ''}
${dateStr} ${escape(m.subject || '—')} ${escape(m.sender || '—')} ${m.finalFile ? '' : ''}
`} ${stats.urgent > 0 ? `

🔴 المعاملات العاجلة هذا الشهر (${stats.urgent})

` : ''} ${(() => { const pendingLetters = monthMemos.filter(m => m.hasLetter && !m.letterApproved); return pendingLetters.length > 0 ? `

⚠️ خطابات بانتظار الاعتماد (${pendingLetters.length})

` : ''; })()}
`; // فتح في نافذة جديدة const reportWindow = window.open('', '_blank', 'width=1100,height=850,scrollbars=yes'); if (!reportWindow) { alert('⚠️ يرجى السماح للنوافذ المنبثقة (Pop-ups) لعرض التقرير'); return; } reportWindow.document.write(reportHTML); reportWindow.document.close(); showToast(`📊 التقرير الشهري جاهز (${stats.total} معاملة)`, 'success', 3000); } // ===== استيراد ودمج ملفات الزملاء ===== async function importFiles() { // === طريقة 1: استخدام File System Access API (Edge/Chrome) === if ('showDirectoryPicker' in window) { try { let dirHandle = savedFolderHandle; // إذا كان لدينا مرجع مجلد محفوظ، نتحقق من صلاحيته if (dirHandle) { try { const permission = await dirHandle.queryPermission({ mode: 'read' }); if (permission !== 'granted') { const newPermission = await dirHandle.requestPermission({ mode: 'read' }); if (newPermission !== 'granted') { dirHandle = null; } } } catch(e) { dirHandle = null; } } // أول مرة - اطلب اختيار المجلد if (!dirHandle) { showToast('📂 اختاري مجلد OneDrive (مرة واحدة فقط)', 'info', 3000); dirHandle = await window.showDirectoryPicker({ id: 'memos-import-folder', startIn: 'documents', mode: 'read' }); savedFolderHandle = dirHandle; await saveFolderHandleToIDB(dirHandle); showToast('✅ تم تذكّر المجلد. الاستيراد القادم سيكون أسرع!', 'success', 3500); } // قراءة كل ملفات JSON من المجلد const files = []; for await (const entry of dirHandle.values()) { if (entry.kind === 'file' && entry.name.endsWith('.json') && entry.name.startsWith('memos-')) { // تجاهل ملفك الخاص const myPrefix = getMyPrefix(); if (entry.name === `memos-${myPrefix}.json`) continue; const file = await entry.getFile(); files.push(file); } } if (files.length === 0) { showToast('ℹ️ لم يتم العثور على ملفات للاستيراد', 'info'); return; } // معالجة الملفات await processImportFiles(files); return; } catch (e) { if (e.name === 'AbortError') return; console.warn('فشل الاستيراد التلقائي:', e); // ارجع للطريقة التقليدية } } // === طريقة 2: الطريقة التقليدية (احتياطي) === const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.multiple = true; input.onchange = async (e) => { const files = Array.from(e.target.files); if (files.length === 0) return; await processImportFiles(files); }; input.click(); } // معالجة ملفات الاستيراد async function processImportFiles(files) { let totalAdded = 0, totalUpdated = 0, totalUnchanged = 0; const newUpdates = []; for (const file of files) { try { const text = await file.text(); const data = JSON.parse(text); if (!data.memos || !Array.isArray(data.memos)) { showToast(`⚠️ ملف ${file.name} غير صالح`, 'error'); continue; } const sourceName = data.owner || 'غير معروف'; for (const importedMemo of data.memos) { const existing = memos.find(m => m.id === importedMemo.id); if (!existing) { // مذكرة جديدة memos.push({ ...importedMemo, owner: sourceName }); totalAdded++; updatedIds.add(importedMemo.id); newUpdates.push({ type: 'new', memoNumber: importedMemo.memoNumber, source: sourceName, time: new Date() }); } else { // مقارنة التحديث const existingUpdated = new Date(existing.updatedAt || existing.createdAt || 0); const importedUpdated = new Date(importedMemo.updatedAt || importedMemo.createdAt || 0); if (importedUpdated > existingUpdated) { // حدِّث المذكرة const changes = detectChanges(existing, importedMemo); Object.assign(existing, importedMemo, { owner: sourceName }); totalUpdated++; updatedIds.add(importedMemo.id); newUpdates.push({ type: 'update', memoNumber: importedMemo.memoNumber, source: sourceName, time: new Date(), changes: changes }); } else { totalUnchanged++; } } } } catch (err) { console.error('خطأ في قراءة الملف:', file.name, err); showToast(`❌ فشل قراءة ${file.name}`, 'error'); } } // حفظ محلياً saveMemos(); render(); updateBackupText(); set('last-import-time', Date.now().toString()); $('sync-reminder').style.display = 'none'; // عرض ملخص const summary = `📥 تم الاستيراد:\n` + `✨ ${totalAdded} جديدة\n` + `🔄 ${totalUpdated} محدّثة\n` + `✓ ${totalUnchanged} بدون تغيير`; showToast(summary, 'success', 5000); // إضافة للتحديثات الأخيرة recentUpdates = [...newUpdates, ...recentUpdates].slice(0, 20); renderUpdatesList(); // إشعار صوتي/مرئي if (totalAdded > 0 || totalUpdated > 0) { flashUpdatedMemos(); } } // حفظ مرجع المجلد في IndexedDB async function saveFolderHandleToIDB(handle) { try { const dbReq = indexedDB.open('memos-app', 1); dbReq.onupgradeneeded = e => e.target.result.createObjectStore('handles'); dbReq.onsuccess = () => { const db = dbReq.result; const tx = db.transaction('handles', 'readwrite'); tx.objectStore('handles').put(handle, 'import-folder'); }; } catch (e) { console.warn('IndexedDB folder save failed:', e); } } // استرجاع مرجع المجلد من IndexedDB async function loadFolderHandleFromIDB() { return new Promise((resolve) => { try { const dbReq = indexedDB.open('memos-app', 1); dbReq.onupgradeneeded = e => e.target.result.createObjectStore('handles'); dbReq.onsuccess = () => { const db = dbReq.result; const tx = db.transaction('handles', 'readonly'); const req = tx.objectStore('handles').get('import-folder'); req.onsuccess = () => resolve(req.result || null); req.onerror = () => resolve(null); }; dbReq.onerror = () => resolve(null); } catch (e) { resolve(null); } }); } // كشف التغييرات بين مذكرتين function detectChanges(oldM, newM) { const changes = []; const fieldsAr = { direction: 'التوجيه', memoType: 'نوع المذكرة', topic: 'الموضوع', sender: 'الجهة المرسلة', letterNumber: 'رقم الخطاب' }; for (const key in fieldsAr) { if ((oldM[key] || '') !== (newM[key] || '')) { changes.push({ field: fieldsAr[key], from: oldM[key] || '—', to: newM[key] || '—' }); } } return changes; } // وميض المذكرات المحدثة function flashUpdatedMemos() { setTimeout(() => { updatedIds.forEach(id => { const row = document.querySelector(`[data-id="${id}"]`); if (row) { row.classList.add('updated-flash'); setTimeout(() => row.classList.remove('updated-flash'), 3000); } }); // امسح القائمة بعد 30 ثانية setTimeout(() => updatedIds.clear(), 30000); }, 100); } // ===== المزامنة التلقائية كل دقيقة ===== // هذه الدالة تحاول إعادة استيراد من آخر مجلد تم استخدامه async function autoSyncCheck() { // في بيئة المتصفح، لا يمكننا قراءة الملفات تلقائياً // لكن يمكن إظهار تذكير إذا مرّ وقت طويل بدون تحديث const lastImport = get('last-import-time'); if (!lastImport) return; const hoursSince = (Date.now() - parseInt(lastImport)) / (1000 * 60 * 60); if (hoursSince > 4) { // مر أكثر من 4 ساعات const reminder = $('sync-reminder'); if (reminder) reminder.style.display = 'block'; } } function toggleAutoSync() { autoSyncEnabled = !autoSyncEnabled; set('auto-sync', autoSyncEnabled ? '1' : '0'); if (autoSyncEnabled) { startAutoSync(); showToast('✅ التذكير التلقائي مفعّل', 'success'); } else { stopAutoSync(); showToast('⏸️ التذكير التلقائي متوقف', 'info'); } } function startAutoSync() { stopAutoSync(); autoSyncInterval = setInterval(autoSyncCheck, 60000); // كل دقيقة autoSyncCheck(); } function stopAutoSync() { if (autoSyncInterval) { clearInterval(autoSyncInterval); autoSyncInterval = null; } } // ===== إشعارات Toast ===== function showToast(message, type = 'info', duration = 3500) { let container = $('toast-container'); if (!container) { container = document.createElement('div'); container.id = 'toast-container'; container.style.cssText = 'position:fixed;top:20px;left:20px;z-index:99999;display:flex;flex-direction:column;gap:10px;'; document.body.appendChild(container); } const toast = document.createElement('div'); const bg = type === 'success' ? '#10b981' : type === 'error' ? '#ef4444' : type === 'warning' ? '#f59e0b' : '#3b82f6'; toast.style.cssText = `background:${bg};color:white;padding:14px 20px;border-radius:10px;box-shadow:0 6px 20px rgba(0,0,0,0.2);font-weight:600;max-width:400px;white-space:pre-line;animation:slideIn 0.3s ease;`; toast.textContent = message; container.appendChild(toast); setTimeout(() => { toast.style.transition = 'opacity 0.4s'; toast.style.opacity = '0'; setTimeout(() => toast.remove(), 400); }, duration); } // ===== عرض سجل التحديثات ===== function renderUpdatesList() { const panel = $('updates-panel'); const list = $('updates-list'); if (!panel || !list) return; if (recentUpdates.length === 0) { panel.style.display = 'none'; return; } panel.style.display = 'block'; list.innerHTML = recentUpdates.slice(0, 10).map(u => { const icon = u.type === 'new' ? '✨' : '🔄'; const action = u.type === 'new' ? 'أضاف' : 'حدّث'; const timeAgo = getTimeAgo(u.time); let details = ''; if (u.changes && u.changes.length > 0) { details = ' (' + u.changes.map(c => c.field).join('، ') + ')'; } return `
${icon} ${escape(u.source)} ${action} المذكرة ${escape(u.memoNumber)}${details} ${timeAgo}
`; }).join(''); } function getTimeAgo(date) { const now = new Date(); const diff = Math.floor((now - date) / 1000); // بالثواني if (diff < 60) return 'قبل ثواني'; if (diff < 3600) return `قبل ${Math.floor(diff/60)} دقيقة`; if (diff < 86400) return `قبل ${Math.floor(diff/3600)} ساعة`; return `قبل ${Math.floor(diff/86400)} يوم`; } function clearUpdatesList() { recentUpdates = []; updatedIds.clear(); renderUpdatesList(); render(); } // ===== إعداد الموظف الحالي ===== function setCurrentEmployee(name) { currentEmployee = name; set('current-employee', name); const prefix = getMyPrefix(); updateSyncStatus('online', `${currentEmployee} (${prefix}-)`); updateMemoNumberField(); } // ===== البحث الذكي ===== function smartSearch(query) { if (!query || !query.trim()) { render(); return; } const q = query.trim().toLowerCase(); const filtered = memos.filter(m => { return (m.memoNumber || '').toLowerCase().includes(q) || (m.topic || '').toLowerCase().includes(q) || (m.letterNumber || '').toLowerCase().includes(q) || (m.sender || '').toLowerCase().includes(q) || (m.owner || '').toLowerCase().includes(q) || (m.direction || '').toLowerCase().includes(q); }); renderFiltered(filtered); } // ==== نهاية كود OneDrive ==== // دوال متروكة للتوافق (لن تُستخدم) function syncToCloud(memo) { /* تصدير يدوي فقط */ } function deleteFromCloud(memoId) { /* تصدير يدوي فقط */ } function showLogin() { init(); } function login(user) { init(); } function logout() { location.reload(); } function escape(s) { if (s == null) return ''; return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); } function fmtDate(iso) { if (!iso) return '—'; const d = new Date(iso); return d.toLocaleDateString('ar-EG', { day:'2-digit', month:'2-digit', year:'numeric' }); } function fileIcon(t) { if (!t) return '📄'; if (t.includes('pdf')) return '📕'; if (t.includes('presentation')||t.includes('powerpoint')) return '📊'; if (t.includes('word')||t.includes('document')) return '📝'; if (t.includes('image')) return '🖼️'; return '📄'; } function readFile(f) { return new Promise((res, rej) => { const r = new FileReader(); r.onload = () => res(r.result); r.onerror = rej; r.readAsDataURL(f); }); } function generateAutoMemoNumber() { const prefix = getMyPrefix(); // ابحث عن المذكرات بنفس البادئة للموظف الحالي const myMemos = memos.filter(m => m.memoNumber && m.memoNumber.startsWith(prefix + '-')); let max = 0; myMemos.forEach(m => { // تحويل أي أرقام عربية إلى لاتينية للمقارنة const en = m.memoNumber.replace(/[٠-٩]/g, d => '٠١٢٣٤٥٦٧٨٩'.indexOf(d)); const mt = en.match(new RegExp(prefix + '-(\\d+)')); if (mt) { const n = parseInt(mt[1]); if (n > max) max = n; } }); // استخدام أرقام لاتينية مع البادئات اللاتينية (AR-1، AM-1، LA-1) return prefix + '-' + (max + 1); } function updateMemoNumberField() { if (editingId) return; const m = $('f-memo'); m.value = generateAutoMemoNumber(); m.readOnly = true; m.style.background = '#f8f9fa'; } function init() { if (inited) return; inited = true; loadMemos(); loadReports(); // تحميل الموظف الحالي currentEmployee = get('current-employee') || null; if (!currentEmployee) { // أول استخدام - اطلب اختيار الموظف setTimeout(() => showEmployeeSelector(), 300); } else { updateSyncStatus('online', `${currentEmployee} (${getMyPrefix()}-)`); } // تحميل إعدادات التحديث التلقائي autoSyncEnabled = get('auto-sync') !== '0'; setupEvents(); setupDragDrop(); $('today-date').textContent = new Date().toLocaleDateString('ar-EG', { weekday:'long', day:'numeric', month:'long' }); updateMemoNumberField(); updateBackupText(); renderReports(); // محاولة استرجاع مرجع الملف للتصدير الصامت if ('showSaveFilePicker' in window) { loadFileHandleFromIDB().then(handle => { if (handle) { savedFileHandle = handle; console.log('✅ تم استرجاع مرجع ملف التصدير - التصدير سيكون صامتاً'); } }); } // محاولة استرجاع مرجع المجلد للاستيراد التلقائي if ('showDirectoryPicker' in window) { loadFolderHandleFromIDB().then(handle => { if (handle) { savedFolderHandle = handle; console.log('✅ تم استرجاع مرجع مجلد الاستيراد'); } }); } // محاولة استرجاع مرجع ملف النسخة الاحتياطية if ('showSaveFilePicker' in window) { loadBackupHandleFromIDB().then(handle => { if (handle) { savedBackupHandle = handle; console.log('✅ تم استرجاع مرجع ملف النسخة الاحتياطية'); } }); } // بدء الفحص التلقائي if (autoSyncEnabled) startAutoSync(); // تحقق من المواعيد العاجلة checkUrgentDeadlines(); } // فحص المواعيد العاجلة وتنبيه المستخدم function checkUrgentDeadlines() { const today = new Date(); today.setHours(0,0,0,0); const upcomingUrgent = memos.filter(m => { if (m.priority !== 'urgent') return false; if (m.finalFile) return false; // مكتملة if (!m.urgentDeadline) return false; const deadline = new Date(m.urgentDeadline); deadline.setHours(0,0,0,0); const reminderDays = parseInt(m.reminderBefore || 1); const reminderDate = new Date(deadline); reminderDate.setDate(reminderDate.getDate() - reminderDays); return today >= reminderDate; }); if (upcomingUrgent.length > 0) { setTimeout(() => { const list = upcomingUrgent.map(m => { const days = Math.ceil((new Date(m.urgentDeadline) - today) / 86400000); let status = ''; if (days < 0) status = `🚨 متأخرة بـ ${Math.abs(days)} يوم`; else if (days === 0) status = '⚠️ اليوم!'; else if (days === 1) status = '⏰ غداً'; else status = `📅 خلال ${days} أيام`; return `• ${m.memoNumber}: ${m.subject || ''} - ${status}`; }).join('\n'); showToast(`🔴 لديكِ ${upcomingUrgent.length} مذكرة عاجلة!\n${list}`, 'warning', 8000); }, 1500); } } // نافذة اختيار الموظف function showEmployeeSelector() { const overlay = document.createElement('div'); overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.75);z-index:99999;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(4px);'; overlay.innerHTML = `

👋 مرحباً بكِ!

من فضلك اختر اسمك لبدء العمل

`; document.body.appendChild(overlay); overlay.querySelectorAll('.emp-btn').forEach(btn => { btn.addEventListener('mouseenter', () => { btn.style.borderColor = '#3b82f6'; btn.style.background = '#eff6ff'; }); btn.addEventListener('mouseleave', () => { btn.style.borderColor = '#e2e8f0'; btn.style.background = 'white'; }); btn.addEventListener('click', () => { setCurrentEmployee(btn.dataset.name); overlay.remove(); showToast(`✅ أهلاً ${btn.dataset.name}!`, 'success'); }); }); } function loadMemos() { try { memos = JSON.parse(get(KEYS.memos) || '[]'); } catch(e) { memos = []; } updateSources(); render(); } function saveMemos() { set(KEYS.memos, JSON.stringify(memos)); } function updateSources() { /* لم تعد مستخدمة */ } function updateBackupText() { const d = get(KEYS.backup); if (!d) return; const dt = new Date(d); const days = Math.floor((Date.now() - dt.getTime())/86400000); let t = 'آخر نسخة: ' + dt.toLocaleDateString('ar-EG'); if (days > 7) t += ' ⚠'; $('last-backup').textContent = t; } function renderConditional() { const dir = $('f-direction').value; const w = $('conditional-fields'); // إظهار/إخفاء منطقة للإفادة/لإبداء الرأي const replySection = $('reply-section'); const filesContainer = $('files-sections'); const showReply = (dir === 'للإفادة / لإبداء الرأي'); if (showReply) { replySection.style.display = 'block'; filesContainer.classList.add('has-reply'); } else { replySection.style.display = 'none'; filesContainer.classList.remove('has-reply'); } // الحقول الشرطية للإفادة/لإبداء الرأي if (dir !== 'للإفادة / لإبداء الرأي') { w.innerHTML = ''; w.classList.remove('show'); return; } let html = '
'; html += '
'; html += '
'; html += '
'; w.innerHTML = html; w.classList.add('show'); if (editingId) { const m = memos.find(x => x.id === editingId); if (m) { if ($('f-assigned')) $('f-assigned').value = m.assignedTo || ''; if ($('f-deadline')) $('f-deadline').value = m.deadline || ''; if ($('f-notes')) $('f-notes').value = m.notes || ''; } } } function setupDragDrop() { const zones = [ { zone: $('progress-drop'), target: 'progress' }, { zone: $('reply-drop'), target: 'reply' }, { zone: $('sent-drop'), target: 'sent' }, { zone: $('final-drop'), target: 'final' } ]; zones.forEach(({ zone, target }) => { if (!zone) return; ['dragenter','dragover'].forEach(ev => zone.addEventListener(ev, e => { e.preventDefault(); e.stopPropagation(); zone.classList.add('drag-over'); })); ['dragleave','drop'].forEach(ev => zone.addEventListener(ev, e => { e.preventDefault(); e.stopPropagation(); zone.classList.remove('drag-over'); })); zone.addEventListener('drop', async e => { const files = Array.from(e.dataTransfer.files); if (target === 'final' && files[0]) await setFinalFile(files[0]); else for (const f of files) await addFileTo(f, target); }); // === ميزة اللصق الذكي === // يقبل: ملفات (Ctrl+C على ملف ثم Ctrl+V) أو روابط نصية zone.setAttribute('tabindex', '0'); zone.addEventListener('paste', async e => { e.preventDefault(); const cd = e.clipboardData || window.clipboardData; if (!cd) return; // 1. أولاً: نتحقق من وجود ملفات في الحافظة const files = Array.from(cd.files || []); if (files.length > 0) { // نسخ ولصق ملف من File Explorer! for (const f of files) { if (target === 'final') { await setFinalFile(f); break; } else { await addFileTo(f, target); } } showToast(`✅ تم لصق ${files.length} ملف`, 'success', 2000); return; } // 2. ثانياً: نتحقق من النص (رابط) const text = cd.getData('text'); if (text && isValidLink(text.trim())) { await addLinkTo(text.trim(), target); zone.classList.remove('drag-over'); showToast('✅ تم إضافة الرابط بنجاح', 'success', 2000); } else if (text && text.trim()) { // قد يكون مساراً محلياً (Copy as path) if (looksLikePath(text.trim())) { await addPathTo(text.trim(), target); showToast('✅ تم إضافة الملف من المسار', 'success', 2500); } else { showToast('⚠️ لا يوجد ملف أو رابط صالح في الحافظة\nانسخ ملفاً من File Explorer أو رابط OneDrive', 'warning', 4000); } } }); // إظهار شارة "الصق هنا" عند التركيز zone.addEventListener('focus', () => zone.classList.add('drag-over')); zone.addEventListener('blur', () => zone.classList.remove('drag-over')); // النقر على الزون يركّز عليه (للسماح بـ Ctrl+V) zone.addEventListener('click', () => zone.focus()); }); let dc = 0; document.addEventListener('dragenter', e => { e.preventDefault(); dc++; if (e.dataTransfer?.types?.includes('Files')) $('drag-hint').classList.add('show'); }); document.addEventListener('dragleave', () => { dc--; if (dc <= 0) { $('drag-hint').classList.remove('show'); dc = 0; } }); document.addEventListener('drop', e => { e.preventDefault(); dc = 0; $('drag-hint').classList.remove('show'); }); document.addEventListener('dragover', e => e.preventDefault()); } // التحقق من أن النص يبدو كمسار ملف function looksLikePath(text) { if (!text) return false; const t = text.trim().replace(/^"|"$/g, ''); // إزالة علامات الاقتباس // مسار ويندوز (C:\ أو D:\) أو مسار يبدأ بـ \\ if (/^[a-zA-Z]:[\\\/]/.test(t)) return true; if (t.startsWith('\\\\')) return true; return false; } // إضافة ملف من المسار (Copy as path) async function addPathTo(path, target) { // إزالة علامات الاقتباس const cleanPath = path.replace(/^"|"$/g, '').trim(); // استخراج اسم الملف من المسار const fileName = cleanPath.split(/[\\\/]/).pop() || 'ملف'; const type = detectFileType(fileName); const entry = { name: fileName, type: type, size: 0, data: null, isLink: true, isPath: true, // علامة لتمييز المسار المحلي url: '', // لا نستخدم URL للمسارات path: cleanPath // المسار الكامل }; if (target === 'progress') { progressFiles.push(entry); renderProgressFiles(); } else if (target === 'reply') { replyFiles.push(entry); renderReplyFiles(); } else if (target === 'sent') { sentFiles.push(entry); renderSentFiles(); } else if (target === 'final') { currentFinal = entry; renderFinalPreview(); const wrap = $('sent-date-wrap'); if (wrap) wrap.classList.add('show'); if (!$('f-sent').value) { $('f-sent').value = new Date().toISOString().slice(0, 10); } } } // ===== التحقق من صحة الرابط ===== function isValidLink(text) { if (!text || typeof text !== 'string') return false; const t = text.trim(); return t.startsWith('http://') || t.startsWith('https://'); } // ===== استخراج اسم الملف من رابط OneDrive ===== function extractFileNameFromLink(url) { try { // محاولة 1: من باراميتر file= أو filename= const urlObj = new URL(url); const fileParam = urlObj.searchParams.get('file') || urlObj.searchParams.get('filename'); if (fileParam) return decodeURIComponent(fileParam); // محاولة 2: من النهاية بعد آخر / (مع تنظيف query string) let lastPart = url.split('?')[0].split('#')[0].split('/').pop(); if (lastPart) { lastPart = decodeURIComponent(lastPart); // إذا انتهى بامتداد ملف، استخدمه if (/\.(pdf|docx?|pptx?|xlsx?|png|jpe?g|gif|txt|csv|zip|rar)$/i.test(lastPart)) { return lastPart; } } // محاولة 3: اسم افتراضي حسب النطاق if (url.includes('onedrive') || url.includes('1drv') || url.includes('sharepoint')) { return 'ملف OneDrive'; } return 'رابط ملف'; } catch(e) { return 'رابط ملف'; } } // ===== اكتشاف نوع الملف من الرابط/الاسم ===== function detectFileType(name) { const n = (name || '').toLowerCase(); if (n.endsWith('.pdf')) return 'application/pdf'; if (n.endsWith('.pptx') || n.endsWith('.ppt')) return 'application/vnd.openxmlformats-officedocument.presentationml.presentation'; if (n.endsWith('.docx') || n.endsWith('.doc')) return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; if (n.endsWith('.xlsx') || n.endsWith('.xls')) return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; if (/\.(png|jpe?g|gif|webp)$/i.test(n)) return 'image/' + n.split('.').pop(); return 'application/octet-stream'; } // ===== إضافة رابط بدلاً من ملف ===== async function addLinkTo(url, target) { const fileName = extractFileNameFromLink(url); const type = detectFileType(fileName); const entry = { name: fileName, type: type, size: 0, // الحجم غير معروف لروابط data: null, // لا توجد بيانات isLink: true, // علامة للتمييز url: url }; if (target === 'progress') { progressFiles.push(entry); renderProgressFiles(); } else if (target === 'reply') { replyFiles.push(entry); renderReplyFiles(); } else if (target === 'sent') { sentFiles.push(entry); renderSentFiles(); } else if (target === 'final') { currentFinal = entry; renderFinalPreview(); const wrap = $('sent-date-wrap'); if (wrap) wrap.classList.add('show'); if (!$('f-sent').value) { $('f-sent').value = new Date().toISOString().slice(0, 10); } } } // الحد الأقصى للملف الصغير = 1 ميجا const SMALL_FILE_LIMIT = 1 * 1024 * 1024; // عرض رسالة الملف الكبير مع توجيه لـ OneDrive function showLargeFileDialog(file) { const sizeKB = Math.round(file.size / 1024); const sizeMB = (file.size / (1024 * 1024)).toFixed(1); return new Promise((resolve) => { // إنشاء نافذة منبثقة جميلة const overlay = document.createElement('div'); overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.6);z-index:99999;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(4px);padding:20px;'; const modal = document.createElement('div'); modal.style.cssText = 'background:white;border-radius:16px;padding:28px;max-width:520px;width:100%;box-shadow:0 20px 60px rgba(0,0,0,0.25);'; modal.innerHTML = `
📦

الملف كبير الحجم

📄 ${escape(file.name)}
الحجم: ${sizeMB} ميجا (${sizeKB} KB)
💡 الطريقة الأمثل لحفظه:
  1. اذهب لفولدر OneDrive المشترك
  2. احفظ الملف هناك
  3. كليك يمين على الملف → "Copy link"
  4. ارجع للنظام والصق الرابط هنا (Ctrl+V)
⚠️ تحذير: رفع الملفات الكبيرة قد يبطئ النظام أو يفقد البيانات
`; overlay.appendChild(modal); document.body.appendChild(overlay); modal.querySelector('#lf-cancel').onclick = () => { overlay.remove(); resolve('cancel'); }; modal.querySelector('#lf-upload').onclick = () => { overlay.remove(); resolve('upload'); }; modal.querySelector('#lf-link').onclick = () => { // فتح OneDrive في تبويبة جديدة window.open('https://onedrive.live.com', '_blank', 'noopener'); overlay.remove(); resolve('cancel'); }; }); } async function addFileTo(file, target) { // فحص الحجم: ملف صغير = رفع، ملف كبير = توجيه لـ OneDrive if (file.size > SMALL_FILE_LIMIT) { const choice = await showLargeFileDialog(file); if (choice === 'cancel') return; // إذا اختار 'upload' يكمل الرفع رغم الحجم } try { const data = await readFile(file); const entry = { name: file.name, type: file.type, size: file.size, data }; if (target === 'progress') { progressFiles.push(entry); renderProgressFiles(); } else if (target === 'reply') { replyFiles.push(entry); renderReplyFiles(); } else if (target === 'sent') { sentFiles.push(entry); renderSentFiles(); } showToast(`✅ تم رفع: ${file.name}`, 'success', 2000); } catch(e) { console.error('File read error:', e); alert('تعذر قراءة الملف. حاول استخدام رابط OneDrive بدلاً من الرفع المباشر.'); } } async function setFinalFile(file) { if (!file.name.toLowerCase().endsWith('.pdf') && file.type !== 'application/pdf') { alert('الملف النهائي يجب أن يكون PDF'); return; } // فحص الحجم if (file.size > SMALL_FILE_LIMIT) { const choice = await showLargeFileDialog(file); if (choice === 'cancel') return; } try { const data = await readFile(file); currentFinal = { name: file.name, type: file.type, size: file.size, data }; renderFinalPreview(); const wrap = $('sent-date-wrap'); wrap.classList.add('show'); if (!$('f-sent').value) { $('f-sent').value = new Date().toISOString().slice(0, 10); wrap.classList.add('highlight'); setTimeout(() => wrap.classList.remove('highlight'), 3000); } showToast(`✅ تم رفع الملف النهائي`, 'success', 2000); } catch(e) { console.error('File read error:', e); alert('تعذر قراءة الملف. حاول استخدام رابط OneDrive بدلاً من الرفع المباشر.'); } } // تنسيق ملف أو رابط للعرض function formatFileChip(f, idx, removeAttr) { if (f.isPath) { // مسار محلي (من Copy as path) return `📁 ${fileIcon(f.type)} ${escape(f.name)} `; } if (f.isLink) { return `🔗 ${fileIcon(f.type)} ${escape(f.name)} `; } return `${fileIcon(f.type)} ${escape(f.name)} `; } function renderProgressFiles() { $('progress-preview').innerHTML = progressFiles.map((f, i) => formatFileChip(f, i, 'data-rm-prog')).join(''); attachLinkClickHandlers('progress-preview'); } function renderReplyFiles() { $('reply-preview').innerHTML = replyFiles.map((f, i) => formatFileChip(f, i, 'data-rm-reply')).join(''); attachLinkClickHandlers('reply-preview'); } function renderSentFiles() { $('sent-preview').innerHTML = sentFiles.map((f, i) => formatFileChip(f, i, 'data-rm-sent')).join(''); attachLinkClickHandlers('sent-preview'); } // ربط معالجات النقر على أزرار فتح الرابط function attachLinkClickHandlers(containerId) { const container = $(containerId); if (!container) return; container.querySelectorAll('.open-link-btn').forEach(btn => { btn.onclick = (e) => { e.preventDefault(); e.stopPropagation(); const url = btn.getAttribute('data-link-url'); if (url) window.open(url, '_blank', 'noopener,noreferrer'); }; }); container.querySelectorAll('.open-path-btn').forEach(btn => { btn.onclick = (e) => { e.preventDefault(); e.stopPropagation(); const path = btn.getAttribute('data-path'); if (path) { navigator.clipboard.writeText(path).then(() => { showToast(`📋 تم نسخ المسار - الصقه في File Explorer`, 'success', 4000); }).catch(() => { alert(`المسار:\n${path}`); }); } }; }); } function renderFinalPreview() { const text = $('final-text'); if (currentFinal) { if (currentFinal.isLink) { text.innerHTML = `🔗 ${escape(currentFinal.name)}
رابط مرتبط ✓ ↗ فتح`; } else { text.innerHTML = `📕 ${escape(currentFinal.name)}
تم الإرفاق ✓`; } $('final-preview').innerHTML = `
`; $('rm-final').onclick = () => { currentFinal = null; $('f-sent').value = ''; $('sent-date-wrap').classList.remove('show'); renderFinalPreview(); }; $('sent-date-wrap').classList.add('show'); } else { text.innerHTML = '📕 اسحب PDF أو الصق رابط (Ctrl+V)
(عند الاكتمال)'; $('final-preview').innerHTML = ''; $('sent-date-wrap').classList.remove('show'); } } // عرض ملف الخطاب function renderLetterPreview() { const text = $('letter-text'); const preview = $('letter-preview'); if (!text || !preview) return; if (currentLetterFile) { if (currentLetterFile.isLink) { text.innerHTML = `🔗 ${escape(currentLetterFile.name)} ✓`; } else { text.innerHTML = `📨 ${escape(currentLetterFile.name)} ✓`; } preview.innerHTML = `
`; $('rm-letter').onclick = () => { currentLetterFile = null; renderLetterPreview(); }; } else { text.innerHTML = '📨 اسحب أو الصق ملف الخطاب (Ctrl+V)'; preview.innerHTML = ''; } } function clearForm() { editingId = null; $('form-title').textContent = '➕ إضافة مذكرة جديدة'; $('editing-badge').classList.add('hidden'); $('f-memo').value = ''; $('f-letter').value = ''; $('f-sent').value = ''; $('f-memo-kind').value = ''; $('f-sender').value = ''; $('f-subject').value = ''; $('f-direction').value = ''; progressFiles = []; replyFiles = []; sentFiles = []; currentFinal = null; renderProgressFiles(); renderReplyFiles(); if ($('sent-preview')) renderSentFiles(); renderFinalPreview(); renderConditional(); updateMemoNumberField(); if ($('f-reply-notes')) $('f-reply-notes').value = ''; if ($('f-sent-content')) $('f-sent-content').value = ''; // إعادة الأولوية والتقرير للوضع الافتراضي setPriority('normal'); if ($('f-is-report')) $('f-is-report').checked = false; if ($('f-urgent-deadline')) $('f-urgent-deadline').value = ''; if ($('f-reminder-before')) $('f-reminder-before').value = '1'; // إعادة بيانات الخطاب if ($('f-has-letter')) $('f-has-letter').checked = false; if ($('f-letter-num')) $('f-letter-num').value = ''; if ($('f-letter-date')) $('f-letter-date').value = ''; if ($('f-letter-approved')) $('f-letter-approved').checked = false; currentLetterFile = null; if ($('letter-details')) $('letter-details').style.display = 'none'; renderLetterPreview(); window.scrollTo({ top: 0, behavior: 'smooth' }); } function loadInForm(memo) { editingId = memo.id; $('form-title').textContent = '✏️ تعديل المذكرة'; $('editing-badge').classList.remove('hidden'); $('f-memo').value = memo.memoNumber || ''; $('f-memo').readOnly = false; $('f-memo').style.background = 'white'; $('f-letter').value = memo.letterNumber || ''; $('f-sent').value = memo.sentDate || ''; $('f-sender').value = memo.sender || ''; $('f-memo-kind').value = memo.memoKind || ''; $('f-subject').value = memo.subject || ''; $('f-direction').value = memo.direction || ''; progressFiles = [...(memo.progressFiles || memo.files || [])]; replyFiles = [...(memo.replyFiles || [])]; sentFiles = [...(memo.sentFiles || [])]; currentFinal = memo.finalFile ? { ...memo.finalFile } : null; renderProgressFiles(); renderReplyFiles(); renderFinalPreview(); renderConditional(); if ($('sent-preview')) renderSentFiles(); if ($('f-reply-notes')) $('f-reply-notes').value = memo.replyNotes || ''; if ($('f-sent-content')) $('f-sent-content').value = memo.sentContent || ''; if (currentFinal) $('sent-date-wrap').classList.add('show'); // تحميل الأولوية والموعد والتقرير setPriority(memo.priority || 'normal'); if ($('f-is-report')) $('f-is-report').checked = !!memo.isReport; if ($('f-urgent-deadline')) $('f-urgent-deadline').value = memo.urgentDeadline || ''; if ($('f-reminder-before')) $('f-reminder-before').value = memo.reminderBefore || '1'; // تحميل بيانات الخطاب if ($('f-has-letter')) $('f-has-letter').checked = !!memo.hasLetter; if ($('f-letter-num')) $('f-letter-num').value = memo.letterNum || ''; if ($('f-letter-date')) $('f-letter-date').value = memo.letterDate || ''; if ($('f-letter-approved')) $('f-letter-approved').checked = !!memo.letterApproved; currentLetterFile = memo.letterFile || null; if ($('letter-details')) $('letter-details').style.display = memo.hasLetter ? 'block' : 'none'; renderLetterPreview(); window.scrollTo({ top: 0, behavior: 'smooth' }); } function saveMemo() { const memoNum = $('f-memo').value.trim(); const dir = $('f-direction').value; if (!memoNum) { alert('رقم المذكرة مطلوب'); $('f-memo').focus(); return; } if (!dir) { alert('التوجيه مطلوب'); $('f-direction').focus(); return; } const priority = getPriority(); const urgentDeadline = $('f-urgent-deadline') ? $('f-urgent-deadline').value : ''; if (priority === 'urgent' && !urgentDeadline) { if (!confirm('⚠️ اخترتِ "عاجل" بدون تحديد موعد نهائي.\nهل تريدين المتابعة بدون موعد؟')) { $('f-urgent-deadline').focus(); return; } } const data = { memoNumber: memoNum, letterNumber: $('f-letter').value.trim(), sentDate: $('f-sent').value, sender: $('f-sender').value, memoKind: $('f-memo-kind').value, subject: $('f-subject').value.trim(), direction: dir, progressFiles: progressFiles, replyFiles: replyFiles, sentFiles: sentFiles, finalFile: currentFinal, assignedTo: $('f-assigned') ? $('f-assigned').value.trim() : '', deadline: $('f-deadline') ? $('f-deadline').value : '', notes: $('f-notes') ? $('f-notes').value.trim() : '', replyNotes: $('f-reply-notes') ? $('f-reply-notes').value.trim() : '', sentContent: $('f-sent-content') ? $('f-sent-content').value.trim() : '', priority: priority, urgentDeadline: urgentDeadline, reminderBefore: $('f-reminder-before') ? $('f-reminder-before').value : '1', isReport: $('f-is-report') ? $('f-is-report').checked : false, hasLetter: $('f-has-letter') ? $('f-has-letter').checked : false, letterNum: $('f-letter-num') ? $('f-letter-num').value.trim() : '', letterDate: $('f-letter-date') ? $('f-letter-date').value : '', letterApproved: $('f-letter-approved') ? $('f-letter-approved').checked : false, letterFile: currentLetterFile }; let memoToSync; if (editingId) { const i = memos.findIndex(m => m.id === editingId); if (i >= 0) { memos[i] = { ...memos[i], ...data, updatedAt: new Date().toISOString() }; memoToSync = memos[i]; } } else { const newMemo = { id: 'm_' + Date.now() + '_' + Math.random().toString(36).slice(2,7), createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), owner: currentEmployee || 'غير محدد', ...data }; memos.unshift(newMemo); memoToSync = newMemo; } saveMemos(); updateSources(); clearForm(); render(); } function badge(dir) { if (dir === 'قيد الإجراء') return '🔶 قيد الإجراء'; if (dir === 'للإفادة / لإبداء الرأي') return 'للإفادة / لإبداء الرأي'; if (dir === 'لاتخاذ ما يلزم') return 'لاتخاذ ما يلزم'; return ''; } // حساب الأيام المتبقية للموعد function daysUntilDeadline(memo) { if (!memo.deadline || memo.finalFile) return null; const today = new Date(); today.setHours(0,0,0,0); const d = new Date(memo.deadline); return Math.floor((d - today) / 86400000); } // عرض المواضيع العاجلة (موعدها اليوم أو خلال 3 أيام أو متأخرة) function renderUrgentBanner() { const banner = $('urgent-banner'); const list = $('urgent-list'); if (!banner || !list) return; const urgent = memos.filter(m => { const days = daysUntilDeadline(m); return days !== null && days <= 3; }).sort((a, b) => { const da = daysUntilDeadline(a); const db = daysUntilDeadline(b); return da - db; // الأكثر تأخراً أولاً }); if (urgent.length === 0) { banner.classList.add('hidden'); return; } banner.classList.remove('hidden'); // عرض أول 5 مواضيع فقط const toShow = urgent.slice(0, 5); list.innerHTML = toShow.map(m => { const days = daysUntilDeadline(m); let daysText, daysClass = ''; if (days < 0) daysText = `⚠ متأخر ${Math.abs(days)} يوم`; else if (days === 0) { daysText = '🔥 اليوم!'; daysClass = 'today'; } else daysText = `⏰ بعد ${days} ${days === 1 ? 'يوم' : 'أيام'}`; return `
${escape(m.memoNumber)} ${escape(m.subject || 'بدون موضوع')} ${daysText}
`; }).join(''); if (urgent.length > 5) { list.innerHTML += `
+ ${urgent.length - 5} مذكرة عاجلة أخرى
`; } } // تحديد المذكرات المؤرشفة (مكتملة + مضى عليها 30 يوم من الإكمال) function isArchived(m) { if (!m.finalFile) return false; // نستخدم تاريخ الإرسال إن وُجد، وإلا تاريخ التحديث، وإلا تاريخ الإنشاء const refDate = m.sentDate || m.updatedAt || m.createdAt; if (!refDate) return false; const d = new Date(refDate); const today = new Date(); const daysDiff = Math.floor((today - d) / 86400000); return daysDiff > 30; } function renderArchive() { const tbody = $('archive-tbody'); const count = $('archive-count'); if (!tbody || !count) return; const archived = memos.filter(isArchived).sort((a, b) => { const da = a.sentDate || a.createdAt || ''; const db = b.sentDate || b.createdAt || ''; return db.localeCompare(da); }); count.textContent = archived.length; if (archived.length === 0) { tbody.innerHTML = '📭 لا توجد مذكرات مؤرشفة بعد'; return; } tbody.innerHTML = archived.map((m, i) => { const final = m.finalFile ? makePreviewBox(m.finalFile, m.id, 'final', 0) : '—'; const archiveDate = m.sentDate ? fmtDate(m.sentDate) : (m.createdAt ? fmtDate(m.createdAt.slice(0,10)) : '—'); return ` ${i + 1} ${escape(m.memoNumber)} ${escape(m.sender || '—')} ${escape(m.subject || '—')} ${badge(m.direction)} ${final} ${archiveDate} `; }).join(''); } function extraDetails(m) { const parts = []; if (m.assignedTo) parts.push(`
إلى: ${escape(m.assignedTo)}
`); if (m.deadline && !m.finalFile) { const today = new Date(); today.setHours(0,0,0,0); const d = new Date(m.deadline); const diff = Math.floor((d - today) / 86400000); let tag; if (diff < 0) tag = `⚠ متأخر ${Math.abs(diff)}ي`; else if (diff <= 3) tag = `⏰ ${diff===0?'اليوم':diff+'ي'}`; else tag = `📅 ${fmtDate(m.deadline)}`; parts.push(`
${tag}
`); } if (m.opinion) parts.push(`
الرأي: ${escape(m.opinion)}
`); if (m.notes) parts.push(`
${escape(m.notes)}
`); return parts.join('') || ''; } function makePreviewBox(file, id, section, idx) { let thumb; if (file.isLink) { // للروابط: نعرض أيقونة كبيرة بدلاً من معاينة thumb = `🔗${fileIcon(file.type)}`; } else if (file.type && file.type.startsWith('image/')) thumb = ``; else if (file.type === 'application/pdf') thumb = ``; else thumb = `${fileIcon(file.type)}`; const cls = section === 'final' ? 'final' : (section === 'reply' ? 'reply' : (section === 'sent' ? 'reply' : 'progress')); const linkAttr = file.isLink ? `data-link-url="${escape(file.url)}"` : ''; return `
${thumb}
${file.isLink ? '↗' : '🔍'}
${escape(file.name)}
`; } function render() { const search = $('search') ? $('search').value.toLowerCase().trim() : ''; // استثناء المذكرات المؤرشفة من الجدول الرئيسي const activeMemos = memos.filter(m => !isArchived(m)); let filtered = activeMemos.filter(m => { if (quickFilter === 'completed' && !m.finalFile) return false; if (quickFilter === 'reports' && !m.isReport) return false; if (quickFilter === 'urgent' && (m.priority !== 'urgent' || m.finalFile)) return false; if (['قيد الإجراء','للإفادة / لإبداء الرأي','لاتخاذ ما يلزم'].includes(quickFilter) && m.direction !== quickFilter) return false; if (!search) return true; // بحث ذكي شامل في كل الحقول const allFields = [ m.memoNumber, m.letterNumber, m.owner, m.sender, m.subject, m.direction, m.memoKind, m.notes, m.replyNotes, m.sentContent, m.assignedTo, m.deadline, m.urgentDeadline, m.isReport ? 'تقرير' : '', m.priority === 'urgent' ? 'عاجل' : '', m.finalFile ? 'مكتمل مكتملة' : '' ].filter(Boolean).join(' ').toLowerCase(); return allFields.includes(search); }); renderMemosList(filtered, activeMemos); } function renderFiltered(filtered) { const activeMemos = memos.filter(m => !isArchived(m)); renderMemosList(filtered.filter(m => !isArchived(m)), activeMemos); } function renderMemosList(filtered, activeMemos) { $('s-total').textContent = activeMemos.length; $('s-progress').textContent = activeMemos.filter(m => m.direction === 'قيد الإجراء').length; $('s-info').textContent = activeMemos.filter(m => m.direction === 'للإفادة / لإبداء الرأي').length; $('s-action').textContent = activeMemos.filter(m => m.direction === 'لاتخاذ ما يلزم').length; $('s-done').textContent = activeMemos.filter(m => m.finalFile).length; if ($('s-reports')) $('s-reports').textContent = activeMemos.filter(m => m.isReport).length; if ($('s-urgent')) $('s-urgent').textContent = activeMemos.filter(m => m.priority === 'urgent' && !m.finalFile).length; // تحديث التنبيهات العاجلة والأرشيف renderUrgentBanner(); renderArchive(); const tbody = $('tbody'); if (filtered.length === 0) { tbody.innerHTML = `${memos.length === 0 ? '📭 لا توجد مذكرات بعد' : '🔍 لا نتائج'}`; return; } tbody.innerHTML = filtered.map((m, i) => { const pFiles = (m.progressFiles || m.files || []).map((f, fi) => makePreviewBox(f, m.id, 'progress', fi)).join(''); const rFiles = (m.replyFiles || []).map((f, fi) => makePreviewBox(f, m.id, 'reply', fi)).join(''); const sFiles = (m.sentFiles || []).map((f, fi) => makePreviewBox(f, m.id, 'sent', fi)).join(''); const final = m.finalFile ? makePreviewBox(m.finalFile, m.id, 'final', 0) : ''; const cls = m.finalFile ? 'completed' : ''; const createdDateShort = m.createdAt ? fmtDate(m.createdAt.slice(0,10)) : ''; const memoCard = `
${escape(m.memoNumber)} / ${createdDateShort}📋
${m.sentDate ? `
📤 أُرسلت: ${fmtDate(m.sentDate)}
` : ''}
`; // بناء خانة للإفادة/لإبداء الرأي بقسمين let replyCell = ''; const hasSent = m.sentContent || sFiles; const hasReply = m.replyNotes || rFiles; if (hasSent || hasReply) { replyCell = '
'; if (hasSent) { replyCell += '
'; replyCell += '
📤 مُرسل
'; if (m.sentContent) replyCell += `
${escape(m.sentContent)}
`; if (sFiles) replyCell += `
${sFiles}
`; replyCell += '
'; } if (hasReply) { replyCell += '
'; replyCell += '
📨 الرد
'; if (m.replyNotes) replyCell += `
${escape(m.replyNotes)}
`; if (rFiles) replyCell += `
${rFiles}
`; replyCell += '
'; } replyCell += '
'; } else { replyCell = ''; } const adminBtns = ``; const ownerBadge = m.owner && m.owner !== currentEmployee ? `
👤 ${escape(m.owner.split(' ')[0])}
` : ''; // شارة التقرير const reportBadge = m.isReport ? '📊 تقرير ' : ''; // شارة الأولوية let priorityBadge = ''; let urgentInfo = ''; if (m.priority === 'urgent' && !m.finalFile) { priorityBadge = '🔴 عاجل '; if (m.urgentDeadline) { const today = new Date(); today.setHours(0,0,0,0); const deadline = new Date(m.urgentDeadline); deadline.setHours(0,0,0,0); const days = Math.ceil((deadline - today) / 86400000); let info = ''; if (days < 0) info = `🚨 متأخرة ${Math.abs(days)} يوم`; else if (days === 0) info = `⚠️ اليوم!`; else if (days === 1) info = `⏰ غداً`; else if (days <= 3) info = `⏰ خلال ${days} أيام`; else info = `📅 ${fmtDate(m.urgentDeadline)}`; urgentInfo = `
${info}
`; } } return ` ${i + 1} ${reportBadge}${priorityBadge}${memoCard}${ownerBadge}${urgentInfo} ${escape(m.sender || '—')} ${escape(m.letterNumber || '—')} ${escape(m.memoKind || '—')}
${escape(m.subject || '—')}
${pFiles ? '
' + pFiles + '
' : ''} ${badge(m.direction)} ${extraDetails(m)} ${replyCell} ${final} ${adminBtns} `; }).join(''); } function previewFile(id, section, idx) { const m = memos.find(x => x.id === id); if (!m) return; let f; if (section === 'final') f = m.finalFile; else if (section === 'reply') f = (m.replyFiles || [])[idx]; else if (section === 'sent') f = (m.sentFiles || [])[idx]; else f = (m.progressFiles || m.files || [])[idx]; if (!f) return; // === إذا كان الملف رابطاً (OneDrive)، افتحه في تبويبة جديدة مباشرة === if (f.isLink && f.url) { window.open(f.url, '_blank', 'noopener,noreferrer'); showToast(`🔗 فتح ${f.name} في تبويبة جديدة`, 'info', 2000); return; } // === إذا كان مساراً محلياً (Copy as path)، نعرض رسالة مع زر النسخ === if (f.isPath && f.path) { // المتصفحات لا تستطيع فتح مسارات محلية مباشرة لأسباب أمنية // لكن نستطيع نسخ المسار للحافظة وإظهار التعليمات navigator.clipboard.writeText(f.path).then(() => { showToast(`📋 تم نسخ المسار. الصقه في File Explorer (Ctrl+V في شريط العنوان)`, 'success', 5000); }).catch(() => { alert(`المسار:\n${f.path}\n\nانسخه والصقه في File Explorer`); }); return; } // === الملفات العادية المرفوعة - معاينة داخلية === $('preview-title').textContent = f.name; const body = $('preview-body'); if (f.type && f.type.startsWith('image/')) body.innerHTML = ``; else if (f.type === 'application/pdf') body.innerHTML = `
⬇ تنزيل
`; else body.innerHTML = `
${fileIcon(f.type)}

${escape(f.name)}

${Math.round(f.size/1024)} KB

⬇ تنزيل
`; $('preview-modal').classList.add('show'); } function exportBackup() { const b = { version: 4, exportedAt: new Date().toISOString(), count: memos.length, memos }; const blob = new Blob([JSON.stringify(b, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'internal-depts-backup-' + new Date().toISOString().slice(0,10) + '.json'; a.click(); URL.revokeObjectURL(url); set(KEYS.backup, new Date().toISOString()); updateBackupText(); $('settings-menu').classList.remove('show'); setTimeout(() => alert(`✓ تم تصدير ${memos.length} مذكرة`), 100); } async function importBackup(file) { try { const data = JSON.parse(await file.text()); if (!data.memos || !Array.isArray(data.memos)) { alert('الملف غير صالح'); return; } const choice = confirm(`عدد المذكرات: ${data.memos.length}\n\nموافق = دمج\nإلغاء = استبدال الكل`); if (choice) { const ids = new Set(memos.map(m => m.id)); const add = data.memos.filter(m => !ids.has(m.id)); memos = [...add, ...memos]; saveMemos(); render(); alert(`✓ أُضيفت ${add.length} مذكرة`); } else { if (confirm(`⚠ سيتم حذف ${memos.length} مذكرة. متأكد؟`)) { memos = data.memos; saveMemos(); render(); alert('✓ تم الاستبدال'); } } } catch(e) { alert('خطأ: ' + e.message); } $('settings-menu').classList.remove('show'); } function setupEvents() { $('f-direction').onchange = renderConditional; $('f-progress-files').onchange = async e => { for (const f of Array.from(e.target.files || [])) await addFileTo(f, 'progress'); e.target.value = ''; }; $('f-reply-files').onchange = async e => { for (const f of Array.from(e.target.files || [])) await addFileTo(f, 'reply'); e.target.value = ''; }; if ($('f-sent-files')) $('f-sent-files').onchange = async e => { for (const f of Array.from(e.target.files || [])) await addFileTo(f, 'sent'); e.target.value = ''; }; $('f-final').onchange = async e => { if (e.target.files && e.target.files[0]) await setFinalFile(e.target.files[0]); e.target.value = ''; }; // أحداث الخطاب if ($('f-has-letter')) { $('f-has-letter').onchange = (e) => { const details = $('letter-details'); if (details) details.style.display = e.target.checked ? 'block' : 'none'; }; } if ($('f-letter-file')) { $('f-letter-file').onchange = async e => { const file = e.target.files && e.target.files[0]; if (!file) return; // فحص الحجم - نفس قاعدة addFileTo if (file.size > SMALL_FILE_LIMIT) { const choice = await showLargeFileDialog(file); if (choice === 'cancel') { e.target.value = ''; return; } } const reader = new FileReader(); reader.onload = ev => { currentLetterFile = { name: file.name, type: file.type, size: file.size, data: ev.target.result }; renderLetterPreview(); }; reader.readAsDataURL(file); e.target.value = ''; }; } // معالجة سحب وإفلات + لصق رابط لمنطقة الخطاب const letterDrop = $('letter-drop'); if (letterDrop) { ['dragenter','dragover'].forEach(ev => letterDrop.addEventListener(ev, e => { e.preventDefault(); e.stopPropagation(); letterDrop.classList.add('drag-over'); })); ['dragleave','drop'].forEach(ev => letterDrop.addEventListener(ev, e => { e.preventDefault(); e.stopPropagation(); letterDrop.classList.remove('drag-over'); })); letterDrop.addEventListener('drop', async e => { const file = e.dataTransfer.files[0]; if (!file) return; if (file.size > SMALL_FILE_LIMIT) { const choice = await showLargeFileDialog(file); if (choice === 'cancel') return; } const reader = new FileReader(); reader.onload = ev => { currentLetterFile = { name: file.name, type: file.type, size: file.size, data: ev.target.result }; renderLetterPreview(); }; reader.readAsDataURL(file); }); letterDrop.setAttribute('tabindex', '0'); letterDrop.addEventListener('paste', async e => { e.preventDefault(); const cd = e.clipboardData || window.clipboardData; if (!cd) return; const files = Array.from(cd.files || []); if (files.length > 0) { const file = files[0]; if (file.size > SMALL_FILE_LIMIT) { const choice = await showLargeFileDialog(file); if (choice === 'cancel') return; } const reader = new FileReader(); reader.onload = ev => { currentLetterFile = { name: file.name, type: file.type, size: file.size, data: ev.target.result }; renderLetterPreview(); }; reader.readAsDataURL(file); showToast('✅ تم إضافة ملف الخطاب', 'success', 2000); return; } const text = cd.getData('text'); if (text && isValidLink(text.trim())) { const url = text.trim(); currentLetterFile = { name: extractFileNameFromLink(url), type: detectFileType(extractFileNameFromLink(url)), size: 0, data: null, isLink: true, url: url }; renderLetterPreview(); showToast('✅ تم إضافة رابط الخطاب', 'success', 2000); } else if (text && looksLikePath(text.trim())) { const cleanPath = text.trim().replace(/^"|"$/g, ''); const fileName = cleanPath.split(/[\\\/]/).pop() || 'ملف الخطاب'; currentLetterFile = { name: fileName, type: detectFileType(fileName), size: 0, data: null, isLink: true, isPath: true, url: '', path: cleanPath }; renderLetterPreview(); showToast('✅ تم إضافة مسار الخطاب', 'success', 2000); } }); letterDrop.addEventListener('focus', () => letterDrop.classList.add('drag-over')); letterDrop.addEventListener('blur', () => letterDrop.classList.remove('drag-over')); letterDrop.addEventListener('click', () => letterDrop.focus()); } $('progress-preview').onclick = e => { const rm = e.target.getAttribute('data-rm-prog'); if (rm !== null) { progressFiles.splice(parseInt(rm), 1); renderProgressFiles(); } }; $('reply-preview').onclick = e => { const rm = e.target.getAttribute('data-rm-reply'); if (rm !== null) { replyFiles.splice(parseInt(rm), 1); renderReplyFiles(); } }; if ($('sent-preview')) $('sent-preview').onclick = e => { const rm = e.target.getAttribute('data-rm-sent'); if (rm !== null) { sentFiles.splice(parseInt(rm), 1); renderSentFiles(); } }; $('save-btn').onclick = saveMemo; $('clear-btn').onclick = () => { if (editingId || progressFiles.length || replyFiles.length || currentFinal || $('f-memo').value) { if (confirm('مسح النموذج؟')) clearForm(); } else clearForm(); }; $('search').oninput = render; document.querySelectorAll('.quick-filter').forEach(btn => { btn.onclick = () => { document.querySelectorAll('.quick-filter').forEach(b => b.classList.remove('active')); btn.classList.add('active'); quickFilter = btn.getAttribute('data-filter'); render(); }; }); $('tbody').addEventListener('click', e => { const edit = e.target.getAttribute('data-edit'); const del = e.target.getAttribute('data-del'); const box = e.target.closest('.file-preview-box'); const memoCard = e.target.closest('.memo-id-card'); if (edit) { const m = memos.find(x => x.id === edit); if (m) loadInForm(m); } else if (del) { if (confirm('حذف المذكرة؟')) { memos = memos.filter(x => x.id !== del); saveMemos(); deleteFromCloud(del); render(); } } else if (box) { previewFile(box.getAttribute('data-id'), box.getAttribute('data-section'), parseInt(box.getAttribute('data-idx') || '0')); } else if (memoCard) { const textToCopy = memoCard.getAttribute('data-copy'); navigator.clipboard.writeText(textToCopy).then(() => { memoCard.classList.add('copied'); setTimeout(() => memoCard.classList.remove('copied'), 1500); }).catch(() => { // احتياطي للمتصفحات القديمة const ta = document.createElement('textarea'); ta.value = textToCopy; document.body.appendChild(ta); ta.select(); try { document.execCommand('copy'); memoCard.classList.add('copied'); setTimeout(() => memoCard.classList.remove('copied'), 1500); } catch(e) {} document.body.removeChild(ta); }); } }); $('settings-btn').onclick = e => { e.stopPropagation(); $('settings-menu').classList.toggle('show'); }; document.addEventListener('click', () => $('settings-menu').classList.remove('show')); $('sync-status').onclick = showEmployeeSelector; // ==== أزرار نظام OneDrive الجديدة ==== $('export-main-btn').onclick = exportMyData; $('import-main-btn').onclick = importFiles; $('full-backup-btn').onclick = exportFullBackup; $('daily-report-btn').onclick = generateDailyReport; if ($('monthly-report-btn')) $('monthly-report-btn').onclick = generateMonthlyReport; $('change-employee-btn').onclick = () => { $('settings-menu').classList.remove('show'); showEmployeeSelector(); }; $('toggle-auto-sync-btn').onclick = () => { $('settings-menu').classList.remove('show'); toggleAutoSync(); }; $('clear-updates-btn').onclick = clearUpdatesList; $('reminder-import').onclick = importFiles; $('reminder-later').onclick = () => $('sync-reminder').style.display = 'none'; $('clear-test-btn').onclick = () => { $('settings-menu').classList.remove('show'); if (memos.length === 0) { showToast('ℹ️ لا توجد مذكرات لمسحها', 'info'); return; } const count = memos.length; if (confirm(`⚠️ تحذير: سيتم مسح كل المذكرات (${count} مذكرة) من هذا الجهاز.\n\nهل أنتِ متأكدة؟ هذه العملية لا يمكن التراجع عنها.\n\n💡 نصيحة: قومي بـ "تصدير نسخة احتياطية كاملة" أولاً.`)) { if (confirm('⚠️ تأكيد نهائي: هل تريدين فعلاً مسح كل المذكرات؟')) { memos = []; saveMemos(); render(); showToast(`✅ تم مسح ${count} مذكرة. النظام جاهز للبدء من جديد.`, 'success'); } } }; // ============= خانة الأولوية ============= setupPrioritySection(); // الأزرار القديمة (للتوافق) $('export-btn').onclick = exportBackup; $('import-btn').onclick = () => $('import-file').click(); $('import-file').onchange = e => { if (e.target.files[0]) importBackup(e.target.files[0]); e.target.value = ''; }; $('print-btn').onclick = () => { $('settings-menu').classList.remove('show'); window.print(); }; $('preview-close').onclick = () => $('preview-modal').classList.remove('show'); $('preview-modal').addEventListener('click', e => { if (e.target === $('preview-modal')) $('preview-modal').classList.remove('show'); }); // معالج فتح/إغلاق قسم الأرشيف const archToggle = $('archive-toggle'); const archContent = $('archive-content'); if (archToggle) { archToggle.onclick = () => { archToggle.classList.toggle('open'); archContent.classList.toggle('open'); }; } // معالج النقر على المواضيع العاجلة — ينتقل للمذكرة const urgList = $('urgent-list'); if (urgList) { urgList.addEventListener('click', e => { const item = e.target.closest('.urgent-item'); if (item) { const id = item.getAttribute('data-urgent-id'); const m = memos.find(x => x.id === id); if (m) loadInForm(m); } }); } // معالج أحداث جدول الأرشيف (للتعديل والحذف) const archTbody = $('archive-tbody'); if (archTbody) { archTbody.addEventListener('click', e => { const edit = e.target.getAttribute('data-edit'); const del = e.target.getAttribute('data-del'); const box = e.target.closest('.file-preview-box'); if (edit) { const m = memos.find(x => x.id === edit); if (m) loadInForm(m); } else if (del) { if (confirm('حذف المذكرة نهائياً من الأرشيف؟')) { memos = memos.filter(x => x.id !== del); saveMemos(); deleteFromCloud(del); render(); } } else if (box) { previewFile(box.getAttribute('data-id'), box.getAttribute('data-section'), parseInt(box.getAttribute('data-idx') || '0')); } }); } // اختصارات لوحة المفاتيح لسلاسة العمل document.addEventListener('keydown', e => { // Ctrl+Enter لحفظ المذكرة if (e.ctrlKey && e.key === 'Enter') { e.preventDefault(); saveMemo(); } // Esc لإغلاق المعاينة if (e.key === 'Escape') { $('preview-modal').classList.remove('show'); $('settings-menu').classList.remove('show'); } // Ctrl+K للبحث السريع if (e.ctrlKey && e.key === 'k') { e.preventDefault(); $('search').focus(); } // Ctrl+N لمذكرة جديدة (مسح النموذج) if (e.ctrlKey && e.key === 'n') { e.preventDefault(); clearForm(); $('f-memo').focus(); } }); } // ============= خانة الأولوية ============= function setupPrioritySection() { const radios = document.querySelectorAll('input[name="f-priority"]'); radios.forEach(r => { r.addEventListener('change', () => { const isUrgent = document.querySelector('input[name="f-priority"]:checked').value === 'urgent'; $('urgent-deadline-box').style.display = isUrgent ? 'block' : 'none'; }); }); } function getPriority() { const checked = document.querySelector('input[name="f-priority"]:checked'); return checked ? checked.value : 'normal'; } function setPriority(val) { const radio = document.querySelector(`input[name="f-priority"][value="${val || 'normal'}"]`); if (radio) { radio.checked = true; $('urgent-deadline-box').style.display = val === 'urgent' ? 'block' : 'none'; } } // ============= التقارير (مدمجة في المذكرات) ============= // دوال متبقية للتوافق - التقارير الآن جزء من المذكرات (isReport: true) function loadReports() { reports = []; } function saveReports() { /* لا حاجة */ } function renderReports() { /* لا حاجة - تظهر في الجدول العادي */ } // دليل الاستخدام السريع function showGuide() { const overlay = document.createElement('div'); overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.75);z-index:99999;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(4px);overflow-y:auto;padding:20px;'; const closeGuide = () => overlay.remove(); overlay.innerHTML = `

📖 دليل استخدام النظام

🎯 كيف يعمل النظام

هذا النظام يُدار عبر ملفات مشتركة على OneDrive دون الحاجة لمزامنة سحابية معقدة.

1️⃣ في البداية

2️⃣ العمل اليومي

3️⃣ المشاركة مع الفريق

خطوات سهلة:
  1. اضغط زر "💾 تصدير بياناتي" في الأعلى
  2. سيُحفظ ملف باسم memos-AR.json (حسب بادئتك: AR/AM/LA)
  3. احفظ الملف في مجلد مشترك على OneDrive

4️⃣ استلام تحديثات الفريق

  1. اضغط زر "📥 استيراد ودمج"
  2. اختر ملفات الموظفين من المجلد المشترك
  3. يمكن اختيار عدة ملفات دفعة واحدة (Ctrl+click)
  4. سيعرض ملخص: X جديدة، Y محدثة، Z بدون تغيير

5️⃣ الميزات الذكية

💡 نصائح

✨ النظام آمن 100% ولا يعتمد على أي خدمة خارجية
`; document.body.appendChild(overlay); overlay.querySelector('#guide-close').onclick = closeGuide; overlay.addEventListener('click', e => { if (e.target === overlay) closeGuide(); }); } // بدء التطبيق مباشرة init(); })();