document.addEventListener('DOMContentLoaded', () => { /* ====== عناصر ====== */ const els = { lockOverlay: document.getElementById('lockOverlay'), lockInput: document.getElementById('lockInput'), lockBtn: document.getElementById('lockBtn'), lockErr: document.getElementById('lockErr'), drop: document.querySelector('#dropArea, #dropZone'), // دعم كلا المعرفين choose: document.querySelector('#chooseBtn, [data-role="choose"]'), file: document.getElementById('fileInput'), fileName: document.getElementById('fileName'), month: document.getElementById('monthSelect'), week: document.getElementById('weekSelect'), colComplete: document.getElementById('colComplete'), colFullfilled: document.getElementById('colFullfilled'), colInspectorNo: document.getElementById('colInspectorNo'), colInspectorName:document.getElementById('colInspectorName'), colResearcher: document.getElementById('colResearcher'), colPublicId: document.getElementById('colPublicId'), colWeek: document.getElementById('colWeek'), colMonth: document.getElementById('colMonth'), colRegion: document.getElementById('colRegion'), saveCols: document.getElementById('saveCols'), resetCols: document.getElementById('resetCols'), btnProcess: document.getElementById('btnProcess'), btnPer: document.getElementById('btnDownloadPerInspector'), btnMaster: document.getElementById('btnDownloadMaster'), stats: document.getElementById('stats'), noData: document.getElementById('noData'), cards: document.getElementById('cards'), toast: document.getElementById('toast'), }; const show = el => el && el.classList.add('show'); const hide = el => el && el.classList.remove('show'); const toast = (msg, type='info')=>{ const t = els.toast; if (!t) return; t.textContent = msg; t.style.display = 'block'; clearTimeout(t._h); t._h = setTimeout(()=> t.style.display='none', 3500); t.className = 'toast ' + type; }; // تطبيع function toWesternDigits(s){ if (s == null) return ''; const map = {'٠':'0','١':'1','٢':'2','٣':'3','٤':'4','٥':'5','٦':'6','٧':'7','٨':'8','٩':'9'}; return String(s).replace(/[٠-٩]/g, d => map[d]); } function normalizeNum(v){ const s = toWesternDigits(v).trim(); const m = s.match(/\d+/); return m ? String(parseInt(m[0],10)) : s; } function truthy(v){ const s = toWesternDigits(v).toString().trim().toLowerCase(); const neg = ['غير','لا','لم','not','no','غير مكتمل','غير مستوف','غير مستوفي']; if (neg.some(x=> s.includes(x))) return false; const pos = ['true','1','yes','y','نعم','مكتمل','منجز','استوفيت','مستوفي','مستوفى','تم','ok','✓','✔','كلي']; return pos.some(x=> s.includes(x)); } function uniqueNonEmpty(arr){ return [...new Set(arr.map(v => v==null? '' : String(v).trim()).filter(Boolean))]; } function safeName(s){ return String(s||'').replace(/[\\/:*?"<>|]/g,'-').slice(0,50); } function hash32(str){ let h=2166136261>>>0; str=String(str||''); for(let i=0;i>>0; } return h>>>0; } function rng(seed){ let x=hash32(seed)||1; return ()=>{ x^=x<<13; x>>>=0; x^=x>>>17; x^=x<<5; x>>>=0; return (x>>>0)/0x100000000; }; } function pickRandomStable(arr, max, seedKey){ if (arr.length <= max) return arr.slice(); const r=rng(seedKey), a=arr.slice(); for(let i=a.length-1;i>0;i--){ const j=Math.floor(r()*(i+1)); [a[i],a[j]]=[a[j],a[i]]; } return a.slice(0, max); } /* ====== الأعمدة الافتراضية + حفظها ====== */ const DEFAULT_COLS = { nameA:'G', idB:'B', inspectorF:'F', weekH:'H', monthO:'O', regionAR:'AR', fullyMetAB:'AB', completeBL:'BL', inspectorNo:'' // اختياري }; function loadCols(){ try{ return Object.assign({}, DEFAULT_COLS, JSON.parse(localStorage.getItem('colmap')||'{}')); } catch{ return {...DEFAULT_COLS}; } } let COLS = loadCols(); /* ====== شاشة القفل (مرة واحدة) ====== */ (function initLock(){ const passed = localStorage.getItem('passedAuth') === '1'; if (!passed) { show(els.lockOverlay); } else { els.lockOverlay.style.display = 'none'; els.lockOverlay.style.pointerEvents='none'; } els.lockBtn?.addEventListener('click', tryEnter); els.lockInput?.addEventListener('keydown', e=>{ if (e.key==='Enter') tryEnter(); }); function tryEnter(){ const ok = (els.lockInput.value||'').trim() === '2030'; if (ok){ localStorage.setItem('passedAuth','1'); els.lockOverlay.style.display = 'none'; // يمنع اعتراض النقر els.lockOverlay.style.pointerEvents = 'none'; } else { document.getElementById('lockErr').textContent = 'الرقم السري غير صحيح'; } } })(); /* ====== حالة عامة ====== */ const state = { rows: [], cols: [], byInspector: new Map(), filtered: [], seed: null }; /* ====== Worker ====== */ let worker = null; try { worker = new Worker('./worker.js'); } catch(e){ console.warn('Worker fail', e); } if (worker){ worker.onmessage = (e)=>{ const { type, payload, error } = e.data || {}; if (type === 'error') { toast(error || 'خطأ في قراءة الملف','error'); return; } if (type === 'parsed'){ state.rows = payload.rows || []; state.cols = payload.cols || []; fillColSelectors(state.cols); populateMonthWeek(state.rows); state.seed = (()=>{ try{ const a=new Uint32Array(2); crypto.getRandomValues(a); return `${a[0]}-${a[1]}`; }catch{return String(Date.now())} })(); toast(`تمت قراءة ${state.rows.length} صفًا`, 'success'); processData(); // تنفيذ تلقائي } }; } /* ====== رفع الملف (إصلاح النقر + دعم dropZone/Area) ====== */ function wireUploadArea(){ const chooseBtn = els.choose; const drop = els.drop || document.querySelector('[data-role="drop"]'); const file = els.file; if (chooseBtn) chooseBtn.addEventListener('click', ()=> file?.click()); if (drop){ drop.addEventListener('click', ()=> file?.click()); drop.addEventListener('keydown', e=>{ if (e.key==='Enter'||e.key===' ') file?.click(); }); ['dragenter','dragover'].forEach(t => drop.addEventListener(t, e=>{ e.preventDefault(); })); ['dragleave','drop'].forEach(t => drop.addEventListener(t, e=>{ e.preventDefault(); })); drop.addEventListener('drop', e=>{ const f=e.dataTransfer.files?.[0]; if (f) handleFile(f); }); } if (file){ file.addEventListener('change', ()=> handleFile(file.files?.[0])); } } wireUploadArea(); function handleFile(file){ if (!file) return; els.fileName.textContent = `الملف: ${file.name}`; const fr = new FileReader(); fr.onload = e => { const buf = e.target.result; worker?.postMessage({ type:'parse', buffer: buf }, [buf]); toast('جاري قراءة الملف…'); }; fr.onerror = ()=> toast('تعذر قراءة الملف','error'); fr.readAsArrayBuffer(file); } /* ====== تعبئة الشهر/الأسبوع من الملف ====== */ function populateMonthWeek(rows){ const months = uniqueNonEmpty(rows.map(r => normalizeNum(r[COLS.monthO]))); const weeks = uniqueNonEmpty(rows.map(r => normalizeNum(r[COLS.weekH]))); els.month.innerHTML = '' + months.map(v=>``).join(''); els.week.innerHTML = '' + weeks.map(v=>``).join(''); els.month.onchange = processData; els.week.onchange = processData; } /* ====== تعبئة سلكترات الأعمدة ====== */ function fillColSelectors(cols){ const sorted = cols.slice().sort((a,b)=> a.localeCompare(b,'en')); const fill = (sel, def) => { if (!sel) return; sel.innerHTML = ''+sorted.map(c=>``).join(''); if (def && sorted.includes(def)) sel.value = def; }; fill(els.colComplete, COLS.completeBL || 'BL'); fill(els.colFullfilled, COLS.fullyMetAB || 'AB'); fill(els.colInspectorNo, COLS.inspectorNo || ''); fill(els.colInspectorName,COLS.inspectorF || 'F'); fill(els.colResearcher, COLS.nameA || 'G'); fill(els.colPublicId, COLS.idB || 'B'); fill(els.colWeek, COLS.weekH || 'H'); fill(els.colMonth, COLS.monthO || 'O'); fill(els.colRegion, COLS.regionAR || 'AR'); els.saveCols?.addEventListener('click', ()=>{ const map = { completeBL: els.colComplete.value || 'BL', fullyMetAB: els.colFullfilled.value || 'AB', inspectorNo: els.colInspectorNo.value || '', inspectorF: els.colInspectorName.value || 'F', nameA: els.colResearcher.value || 'G', idB: els.colPublicId.value || 'B', weekH: els.colWeek.value || 'H', monthO: els.colMonth.value || 'O', regionAR: els.colRegion.value || 'AR', }; localStorage.setItem('colmap', JSON.stringify(map)); Object.assign(COLS, map); toast('تم الحفظ','success'); }); els.resetCols?.addEventListener('click', ()=>{ localStorage.removeItem('colmap'); Object.assign(COLS, DEFAULT_COLS); fillColSelectors(sorted); toast('تمت إعادة الافتراضيات','info'); }); } /* ====== المعالجة ====== */ const MAX_PER = 5; function processData(){ if (!state.rows.length){ showNoData(true,'لم يتم رفع ملف'); return; } const wantMonth = normalizeNum(els.month.value); const wantWeek = normalizeNum(els.week.value); const ok = state.rows.filter(r=>{ const ab = truthy(r[COLS.fullyMetAB]); const bl = truthy(r[COLS.completeBL]); if (!(ab && bl)) return false; const rm = normalizeNum(r[COLS.monthO] ?? ''); const rw = normalizeNum(r[COLS.weekH] ?? ''); if (wantMonth && rm !== wantMonth) return false; if (wantWeek && rw !== wantWeek) return false; return true; }); // تجميع حسب المفتش const byInspector = new Map(); for (const row of ok){ const inspName = (row[COLS.inspectorF] ?? 'غير محدد') || 'غير محدد'; if (!byInspector.has(inspName)) byInspector.set(inspName, { rowsByRes:new Map(), inspNo: null }); const pack = byInspector.get(inspName); const res = (row[COLS.nameA] ?? 'غير محدد') || 'غير محدد'; if (!pack.rowsByRes.has(res)) pack.rowsByRes.set(res, []); pack.rowsByRes.get(res).push(row); const noCol = COLS.inspectorNo; if (noCol && row[noCol] != null) pack.inspNo = row[noCol]; } // اختيار 5 لكل باحث const out = new Map(); for (const [insp, pack] of byInspector){ const picked = []; for (const [res, arr] of pack.rowsByRes){ picked.push(...pickRandomStable(arr, MAX_PER, `${state.seed}|${insp}|${res}|${wantMonth}|${wantWeek}`)); } out.set(insp, { rows:picked, inspNo: pack.inspNo, researchers: pack.rowsByRes.size }); } state.filtered = ok; state.byInspector = out; render(); } els.btnProcess?.addEventListener('click', processData); /* ====== العرض ====== */ function showNoData(show,msg='لا توجد بيانات'){ els.noData.style.display = show? '' : 'none'; els.noData.textContent = msg; if (show) els.cards.innerHTML = ''; } function render(){ const inspectorCount = [...state.byInspector.keys()].length; let totalSelected = 0; for (const {rows} of state.byInspector.values()) totalSelected += rows.length; els.stats.textContent = `عدد المفتشين: ${inspectorCount} — السجلات المختارة: ${totalSelected} — إجمالي المطابقة قبل الاختيار: ${state.filtered.length}`; const root = els.cards; root.innerHTML = ''; const entries = [...state.byInspector.entries()]; if (!entries.length){ showNoData(true,'لا توجد بيانات مطابقة للإعدادات الحالية'); return; } showNoData(false); for (const [inspName, meta] of entries){ const rows = meta.rows; const researchersCount = meta.researchers || 0; const inspNo = meta.inspNo ? String(meta.inspNo) : '—'; const card = document.createElement('div'); card.className = 'inspector-card'; const h3 = document.createElement('h3'); h3.textContent = `مفتش ${inspName}`; card.appendChild(h3); const kpis = document.createElement('div'); kpis.className = 'kpis'; kpis.innerHTML = `
رقم المفتش${inspNo}
عدد الباحثين${researchersCount}
إجمالي العينة${rows.length}
`; card.appendChild(kpis); const footer = document.createElement('footer'); const hideBtn = document.createElement('button'); hideBtn.textContent='إخفاء'; hideBtn.addEventListener('click', ()=> card.remove()); const dlBtn = document.createElement('button'); dlBtn.textContent='تحميل'; dlBtn.className='primary'; const title = meta.inspNo ? `مفتش ${meta.inspNo}` : `مفتش ${inspName}`; dlBtn.addEventListener('click', ()=> downloadSingleInspector(title, rows)); footer.appendChild(hideBtn); footer.appendChild(dlBtn); card.appendChild(footer); root.appendChild(card); } } /* ====== التنزيل ====== */ async function ensureExcelJS(){ if (!window.ExcelJS){ await new Promise((res,rej)=>{ const s=document.createElement('script'); s.src='https://cdn.jsdelivr.net/npm/exceljs@4.4.0/dist/exceljs.min.js'; s.onload=res; s.onerror=rej; document.head.appendChild(s); }); } } async function buildWorkbookSheets(sheets){ await ensureExcelJS(); const wb=new ExcelJS.Workbook(); sheets.forEach(([name, headers, rows])=>{ const ws=wb.addWorksheet(String(name||'Sheet').slice(0,31)); ws.addRow(headers); rows.forEach(r=>ws.addRow(r)); ws.getRow(1).font={bold:true}; ws.views=[{state:'frozen',ySplit:1}]; ws.columns = headers.map(()=>({width:22})); }); const buf=await wb.xlsx.writeBuffer(); return URL.createObjectURL(new Blob([buf],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'})); } async function downloadSingleInspector(title, rows){ if (!rows.length){ toast('لا توجد بيانات','error'); return; } const headers = ['اسم الباحث','معرف عام','منطقة العد','الأسبوع','الشهر','المفتش']; const data = rows.map(r => [ r[DEFAULT_COLS.nameA] ? r[DEFAULT_COLS.nameA] : r[COLS.nameA] || '', r[DEFAULT_COLS.idB] ? r[DEFAULT_COLS.idB] : r[COLS.idB] || '', r[DEFAULT_COLS.regionAR] ? r[DEFAULT_COLS.regionAR] : r[COLS.regionAR] || '', r[DEFAULT_COLS.weekH] ? r[DEFAULT_COLS.weekH] : r[COLS.weekH] || '', r[DEFAULT_COLS.monthO] ? r[DEFAULT_COLS.monthO] : r[COLS.monthO] || '', r[DEFAULT_COLS.inspectorF] ? r[DEFAULT_COLS.inspectorF] : r[COLS.inspectorF] || '', ]); const href = await buildWorkbookSheets([[title, headers, data]]); const a=document.createElement('a'); a.href=href; a.download=`تقرير_${safeName(title)}.xlsx`; document.body.appendChild(a); a.click(); a.remove(); } els.btnPer?.addEventListener('click', async ()=>{ for (const [insp, meta] of state.byInspector){ const title = meta.inspNo ? `مفتش ${meta.inspNo}` : `مفتش ${insp}`; await downloadSingleInspector(title, meta.rows); } }); els.btnMaster?.addEventListener('click', async ()=>{ const headers = ['اسم الباحث','معرف عام','منطقة العد','الأسبوع','الشهر','المفتش']; const sheets = []; for (const [insp, meta] of state.byInspector){ if (!meta.rows.length) continue; const title = meta.inspNo ? `مفتش ${meta.inspNo}` : `مفتش ${insp}`; const data = meta.rows.map(r => [ r[COLS.nameA]||'', r[COLS.idB]||'', r[COLS.regionAR]||'', r[COLS.weekH]||'', r[COLS.monthO]||'', r[COLS.inspectorF]||'', ]); sheets.push([title, headers, data]); } if (!sheets.length){ toast('لا توجد بيانات','error'); return; } const href = await buildWorkbookSheets(sheets); const a=document.createElement('a'); a.href=href; a.download='تقارير_جميع_المفتشين.xlsx'; document.body.appendChild(a); a.click(); a.remove(); }); }); // DOMContentLoaded