/* طبقة البيانات والفهرسة: تبقى كل المعالجة محلية داخل المتصفح. */ const FIELD_ALIASES = { commercialRegister: ["السجل التجاري", "رقم السجل التجاري", "رقم السجل", "السجل", "commercial register", "cr"], facilityName: ["اسم المنشأة", "إسم المنشأة", "المنشأة", "اسم المنشاه", "facility", "establishment name"], researcher: ["اسم الباحث", "اسم الباحث/ة", "الباحث", "researcher"], collectionStatus: ["حالة الاستيفاء", "حالة جمع البيانات", "collection status"], approvalStatus: ["حالة الاعتماد", "اعتماد", "الوصف", "الوصف.1", "approval"], surveyName: ["اسم المسح", "المسح", "survey", "survey name"], }; const PASSCODE = "2030"; const app = { rows: [], byRegister: new Map(), entityGroups: new Map(), researchers: new Map(), surveys: new Map(), batch: [] }; const $ = (id) => document.getElementById(id); const digits = { "٠":"0", "١":"1", "٢":"2", "٣":"3", "٤":"4", "٥":"5", "٦":"6", "٧":"7", "٨":"8", "٩":"9", "۰":"0", "۱":"1", "۲":"2", "۳":"3", "۴":"4", "۵":"5", "۶":"6", "۷":"7", "۸":"8", "۹":"9" }; function plain(value) { return String(value ?? "").replace(/[\u200E\u200F\u202A-\u202E\u00A0]/g, " ").replace(/\s+/g, " ").trim(); } function normText(value) { return plain(value).toLowerCase().replace(/[إأآا]/g, "ا").replace(/ى/g, "ي").replace(/ة/g, "ه").replace(/[\u064B-\u065F\u0670]/g, ""); } function normalizeDigits(value) { return plain(value).replace(/[٠-٩۰-۹]/g, (c) => digits[c]); } function expandScientific(value) { const m = value.match(/^([+\-]?)(\d+)(?:\.(\d*))?[eE]([+\-]?\d+)$/); if (!m) return value; const sign = m[1] === "-" ? "-" : ""; const whole = m[2]; const fraction = m[3] || ""; const power = Number(m[4]); const all = whole + fraction; const point = whole.length + power; if (!Number.isInteger(power) || point < 0 || point > 80) return value; if (point >= all.length) return sign + all + "0".repeat(point - all.length); if (point <= 0) return sign + "0." + "0".repeat(-point) + all; return sign + all.slice(0, point) + "." + all.slice(point); } function normalizeRegister(value) { let v = expandScientific(normalizeDigits(value)); if (!v || /^(nan|null|undefined)$/i.test(v)) return ""; if (/^\d+\.0+$/.test(v)) v = v.replace(/\.0+$/, ""); if (/^[\d\s,،._\-/]+$/.test(v)) return v.replace(/[\s,،._\-/]/g, ""); return v; } function getField(raw, aliases) { const entries = Object.entries(raw || {}); for (const alias of aliases) { const hit = entries.find(([key]) => normText(key) === normText(alias)); if (hit && plain(hit[1])) return plain(hit[1]); } return ""; } function getStatusClass(value) { const v = normText(value); if (/اعطت كامل|استيفاء ذاتي|مكتمل|معتمد/.test(v) && !/غير/.test(v)) return "ok"; if (/رفض|لم يعثر|مغلق|تعذر|غير مكتمل|ناقص|خارج نطاق/.test(v)) return "danger"; if (/يطلب التواصل|بعض البيانات|جديد|اخرى|تحولت/.test(v)) return "warn"; return ""; } function entityKey(row, id) { return row.commercialRegister ? `cr:${row.commercialRegister}` : `row:${id}`; } function dataPath() { return (typeof SURVEY_DATA_CONFIG !== "undefined" && SURVEY_DATA_CONFIG.excelPath) || "./data/تقاطع.xlsx"; } function rowHasData(raw) { return Object.entries(raw || {}).some(([key, value]) => !String(key).startsWith("__") && plain(value)); } async function readWorkbookRows() { const path = dataPath(); if (typeof XLSX === "undefined") throw new Error("تعذر تحميل مكتبة قراءة Excel."); const response = await fetch(path, { cache: "no-store" }); if (!response.ok) throw new Error(`تعذر العثور على ملف بيانات المسوح. المسار: ${path}`); const workbook = XLSX.read(await response.arrayBuffer(), { type: "array", cellText: true, cellDates: false }); const rows = []; workbook.SheetNames.forEach((sheetName) => { const sheetRows = XLSX.utils.sheet_to_json(workbook.Sheets[sheetName], { defval: "", raw: false, blankrows: false }); sheetRows.filter(rowHasData).forEach((raw) => rows.push({ ...raw, "اسم المسح": sheetName, "__sheet": sheetName })); }); return { rows, path }; } async function loadData() { const source = await readWorkbookRows(); if (!source.rows.length) throw new Error(`تعذر تحميل بيانات المسوح من الملف: ${source.path}`); app.rows = source.rows.map((raw, index) => { const commercialRegister = normalizeRegister(getField(raw, FIELD_ALIASES.commercialRegister)); return { id: index, raw, commercialRegister, sourceSheet: raw.__sheet || getField(raw, FIELD_ALIASES.surveyName), facilityName: getField(raw, FIELD_ALIASES.facilityName), researcher: getField(raw, FIELD_ALIASES.researcher), collectionStatus: getField(raw, FIELD_ALIASES.collectionStatus), approvalStatus: getField(raw, FIELD_ALIASES.approvalStatus), surveyName: getField(raw, FIELD_ALIASES.surveyName) || raw.__sheet || "مسح غير معروف", }; }); app.rows.forEach((row) => { const key = entityKey(row, row.id); if (!app.entityGroups.has(key)) app.entityGroups.set(key, { key, commercialRegister: row.commercialRegister, rows: [] }); app.entityGroups.get(key).rows.push(row); if (row.commercialRegister) { if (!app.byRegister.has(row.commercialRegister)) app.byRegister.set(row.commercialRegister, []); app.byRegister.get(row.commercialRegister).push(row); } const researcherKey = normText(row.researcher); if (researcherKey) { if (!app.researchers.has(researcherKey)) app.researchers.set(researcherKey, { key: researcherKey, name: row.researcher, rows: [] }); app.researchers.get(researcherKey).rows.push(row); } if (!app.surveys.has(row.surveyName)) app.surveys.set(row.surveyName, []); app.surveys.get(row.surveyName).push(row); }); } function escapeHtml(v) { return String(v ?? "").replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); } function td(label, content) { return `${content}`; } function stackedList(items) { const clean = items.map((item) => plain(item)).filter(Boolean); return clean.length ? `
${clean.map((item) => `${escapeHtml(item)}`).join("")}
` : "-"; } function metric(value, label, target = "", description = "") { const action = target ? ` role="button" tabindex="0" data-dashboard-detail="${target}"` : ""; const note = description ? `${escapeHtml(description)}` : ""; return `
${value}${label}${note}
`; } function countBy(rows, picker) { return rows.reduce((map, row) => { const value = picker(row) || "غير محدد"; map.set(value, (map.get(value) || 0) + 1); return map; }, new Map()); } function sharedCount(rows) { return new Set(rows.filter((row) => row.commercialRegister && new Set(app.byRegister.get(row.commercialRegister).map((z) => z.surveyName)).size > 1).map((row) => row.commercialRegister)).size; } function groupInfo(group) { const rows = group.rows; const names = [...new Set(rows.map((row) => row.facilityName).filter(Boolean))]; const surveys = [...new Set(rows.map((row) => row.surveyName))]; const researchers = [...new Set(rows.map((row) => row.researcher).filter(Boolean))]; return { ...group, name: names[0] || "-", names, surveys, researchers, multiSurvey: surveys.length > 1 }; } function groupForRows(rows) { const groups = new Map(); rows.forEach((row) => { const key = entityKey(row, row.id); if (!groups.has(key)) groups.set(key, { key, commercialRegister: row.commercialRegister, rows: [] }); groups.get(key).rows.push(row); }); return [...groups.values()].map(groupInfo); } function unlockApp() { const gate = $("loginGate"); if (!gate || gate.classList.contains("hidden")) return; gate.classList.add("hidden"); setTimeout(() => gate.remove(), 260); setTimeout(() => document.querySelector("[data-view='search']")?.focus(), 300); } function setupLoginGate() { const input = $("passcodeInput"); if (!input) return; input.focus(); $("loginForm")?.addEventListener("submit", (event) => { event.preventDefault(); if (input.value === PASSCODE) unlockApp(); }); input.addEventListener("input", () => { input.value = normalizeDigits(input.value).replace(/\D/g, "").slice(0, 4); if (input.value === PASSCODE) unlockApp(); }); input.addEventListener("keydown", (event) => { if (event.key === "Enter" && input.value === PASSCODE) unlockApp(); }); } function showView(name) { document.querySelectorAll(".view").forEach((view) => view.classList.toggle("active", view.id === name)); document.querySelectorAll(".nav-link").forEach((button) => button.classList.toggle("active", button.dataset.view === name)); window.scrollTo({ top: 0, behavior: "smooth" }); } function toast(message) { const el = $("toast"); el.textContent = message; el.classList.add("show"); clearTimeout(toast.timer); toast.timer = setTimeout(() => el.classList.remove("show"), 3200); } function renderMetrics() { const intersectionGroups = [...app.byRegister.entries()] .map(([register, rows]) => groupInfo({ key: `cr:${register}`, commercialRegister: register, rows })) .filter((group) => group.multiSurvey); const shared = intersectionGroups.length; const statusDifferenceGroups = intersectionGroups.filter((group) => new Set(group.rows.map((row) => plain(row.collectionStatus)).filter(Boolean)).size > 1); $("metrics").innerHTML = [ metric(shared.toLocaleString("ar-SA"), "عدد التقاطعات", "intersections", "سجلات ظهرت في أكثر من مسح"), metric(statusDifferenceGroups.length.toLocaleString("ar-SA"), "اختلاف حالات الاستيفاء", "statuses", "اختلاف حالة المنشأة بين المسوح"), metric(shared.toLocaleString("ar-SA"), "المنشآت المتقاطعة", "establishments", "منشآت مشتركة قابلة للمراجعة"), ].join(""); } function renderEntity(group) { const g = groupInfo(group); const title = g.names.length > 1 ? `${escapeHtml(g.name)} أسماء متعددة` : escapeHtml(g.name); const note = g.multiSurvey ? `موجودة في أكثر من مسح` : `موجودة في مسح واحد`; const occurrences = [...g.rows].sort((a, b) => a.surveyName.localeCompare(b.surveyName, "ar")).map((row) => `
المسح${escapeHtml(row.surveyName)}
الباحث${escapeHtml(row.researcher || "-")}
حالة الاستيفاء${escapeHtml(row.collectionStatus || "-")}
`).join(""); return `

${title}

السجل التجاري: ${escapeHtml(g.commercialRegister || "غير متاح في المصدر")}

${g.surveys.length} مسح${g.rows.length} ظهور${g.researchers.length} باحث${note}
المسح
الباحث
حالة الاستيفاء
${occurrences}
`; } function intersectionGroups() { return [...app.byRegister.entries()] .map(([register, rows]) => groupInfo({ key: `cr:${register}`, commercialRegister: register, rows })) .filter((group) => group.multiSurvey) .sort((a, b) => b.surveys.length - a.surveys.length || a.name.localeCompare(b.name, "ar")); } function dashboardDetailHeader(title, subtitle) { return `

${escapeHtml(title)}

${escapeHtml(subtitle)}

`; } function renderDashboardDetail(type) { const panel = $("dashboardDetails"); if (!panel) return; const groups = intersectionGroups(); const sharedRows = groups.flatMap((group) => group.rows); panel.className = "dashboard-details card"; if (type === "statuses") { const statusDiffs = groups.filter((group) => new Set(group.rows.map((row) => plain(row.collectionStatus)).filter(Boolean)).size > 1); panel.innerHTML = `${dashboardDetailHeader("اختلاف حالات الاستيفاء بين المسوح", "منشآت متقاطعة تختلف حالة الاستيفاء لها من مسح إلى آخر.")}
${statusDiffs.slice(0, 50).map((group) => { const statuses = group.rows .sort((a, b) => a.surveyName.localeCompare(b.surveyName, "ar")) .map((row) => `${row.surveyName}: ${row.collectionStatus || "-"}`); const count = new Set(group.rows.map((row) => plain(row.collectionStatus)).filter(Boolean)).size; return `${td("السجل التجاري", ``)}${td("اسم المنشأة", escapeHtml(group.name))}${td("الحالات حسب المسح", stackedList(statuses))}${td("عدد الحالات", count)}`; }).join("")}
السجل التجارياسم المنشأةالحالات حسب المسحعدد الحالات
`; } else if (type === "researchers") { const researchers = [...countBy(sharedRows, (row) => row.researcher).entries()].sort((a, b) => b[1] - a[1]).slice(0, 20); panel.innerHTML = `${dashboardDetailHeader("الباحثون الذين لديهم تقاطعات", "أعلى الباحثين ظهورًا ضمن المنشآت المتقاطعة بين المسوح.")}
${researchers.map(([name, count]) => `${td("الباحث", escapeHtml(name))}${td("عدد الظهور في التقاطعات", count.toLocaleString("ar-SA"))}`).join("")}
الباحثعدد الظهور في التقاطعات
`; } else { const filtered = groups; const title = type === "establishments" ? "المنشآت المتقاطعة" : "تفاصيل التقاطعات"; const subtitle = "السجلات التجارية التي ظهرت في أكثر من مسح."; panel.innerHTML = `${dashboardDetailHeader(title, subtitle)}
${filtered.slice(0, 50).map((group) => `${td("السجل التجاري", ``)}${td("اسم المنشأة", escapeHtml(group.name))}${td("عدد المسوح", group.surveys.length)}${td("المسوح", stackedList(group.surveys))}${td("الباحثون", stackedList(group.researchers))}`).join("")}
السجل التجارياسم المنشأةعدد المسوحالمسوحالباحثون
`; } panel.scrollIntoView({ behavior: "smooth", block: "start" }); } function search() { const input = $("q").value; const q = normText(input); const reg = normalizeRegister(input); if (!q) { $("searchResults").className = "results empty-state"; $("searchResults").textContent = "ابدأ بالكتابة لعرض النتائج مباشرة."; $("searchMeta").textContent = "البحث يعمل تلقائيًا أثناء الكتابة."; return; } let rows = []; let mode = ""; if (reg && app.byRegister.has(reg)) { rows = app.byRegister.get(reg); mode = "سجل تجاري"; } else { rows = app.rows.filter((row) => normText(row.facilityName).includes(q) || normText(row.researcher).includes(q)); mode = "اسم منشأة أو باحث"; } const groups = groupForRows(rows); $("searchMeta").textContent = groups.length ? `تم العثور على ${groups.length} نتيجة حسب ${mode}.` : "لم يتم العثور على نتائج مطابقة."; $("searchResults").className = "results"; $("searchResults").innerHTML = groups.length ? groups.slice(0, 100).map(renderEntity).join("") : "
لم يتم العثور على نتائج مطابقة.
"; if (groups.length > 100) toast("تم عرض أول 100 نتيجة حفاظًا على سرعة الصفحة."); } function parseBatch(text) { const rawTokens = String(text || "").split(/[\s,،;؛\t\n\r]+/).map(plain).filter(Boolean); const values = []; const seen = new Set(); let invalid = 0; let duplicates = 0; rawTokens.forEach((token) => { const register = normalizeRegister(token); if (!/^\d+$/.test(register)) { invalid += 1; return; } if (seen.has(register)) { duplicates += 1; return; } seen.add(register); values.push(register); }); return { values, duplicates, invalid, entered: rawTokens.length }; } function updateBatchHint() { const parsed = parseBatch($("batchInput").value); if (!parsed.entered) { $("batchHint").textContent = "لم تُدخل أي سجلات بعد."; return; } const ignored = []; if (parsed.duplicates) ignored.push(`${parsed.duplicates} مكرر`); if (parsed.invalid) ignored.push(`${parsed.invalid} يحتوي أحرفًا أو رموزًا`); $("batchHint").textContent = `تم التعرف على ${parsed.values.length} سجل رقمي${ignored.length ? `، وتم تجاهل ${ignored.join(" و")}.` : "."}`; } function batchCheck() { const parsed = parseBatch($("batchInput").value); if (!parsed.values.length) { toast("أدخل أرقام سجلات فقط، بدون أحرف أو رموز."); return; } app.batch = parsed.values.map((register, order) => { const rows = app.byRegister.get(register) || []; const group = rows.length ? groupInfo({ key: `cr:${register}`, commercialRegister: register, rows }) : null; return { order, register, group }; }); const found = app.batch.filter((item) => item.group); const multi = found.filter((item) => item.group.surveys.length > 1); $("batchSummary").innerHTML = [ metric(app.batch.length, "إجمالي السجلات"), metric(found.length, "الموجودة"), metric(app.batch.length - found.length, "غير الموجودة"), metric(found.length - multi.length, "في مسح واحد"), metric(multi.length, "في عدة مسوح"), ].join(""); $("batchMeta").textContent = `تم فحص ${app.batch.length} سجل رقمي بالترتيب الذي أُدخلت به.`; $("batchResults").className = "results"; $("batchResults").innerHTML = renderBatchTable(app.batch); $("btnExport").disabled = false; } function renderBatchTable(items) { return `
${items.map((item) => { const g = item.group; if (!g) return `${td("رقم السجل التجاري", escapeHtml(item.register))}${td("اسم المنشأة", "-")}${td("مرات الظهور", "0")}${td("الباحثون", "-")}${td("حالة المطابقة", `غير موجود`)}`; return `${td("رقم السجل التجاري", ``)}${td("اسم المنشأة", escapeHtml(g.name))}${td("مرات الظهور", g.rows.length)}${td("الباحثون", g.researchers.length)}${td("حالة المطابقة", `${g.multiSurvey ? "موجود في عدة مسوح" : "موجود"}`)}`; }).join("")}
رقم السجل التجارياسم المنشأةمرات الظهورالباحثونحالة المطابقة
`; } function renderIntersections() { const groups = [...app.byRegister.entries()].map(([register, rows]) => groupInfo({ key: `cr:${register}`, commercialRegister: register, rows })).filter((group) => group.multiSurvey).sort((a, b) => b.surveys.length - a.surveys.length); const two = groups.filter((group) => group.surveys.length === 2).length; const three = groups.filter((group) => group.surveys.length >= 3).length; $("intersectionMetrics").innerHTML = [metric(groups.length, "منشآت مشتركة"), metric(two, "في مسحين"), metric(three, "في 3 مسوح أو أكثر"), metric(app.byRegister.size, "سجلات قابلة للمطابقة"), metric(app.surveys.size, "إجمالي المسوح")].join(""); $("intersectionResults").className = "results"; $("intersectionResults").innerHTML = groups.length ? `
${groups.slice(0, 200).map((group) => `${td("السجل التجاري", ``)}${td("اسم المنشأة", escapeHtml(group.name))}${td("عدد المسوح", group.surveys.length)}${td("المسوح", stackedList(group.surveys))}${td("الباحثون", stackedList(group.researchers))}`).join("")}
السجل التجارياسم المنشأةعدد المسوحالمسوحالباحثون
` : "
لا توجد تقاطعات قابلة للحساب.
"; } function renderCompactEntities(groups) { return `
${groups.map((group) => `${td("السجل التجاري", ``)}${td("اسم المنشأة", escapeHtml(group.name))}${td("المسوح", stackedList(group.surveys))}${td("الباحثون", stackedList(group.researchers))}`).join("")}
السجل التجارياسم المنشأةالمسوحالباحثون
`; } function renderResearchers() { const q = normText($("researcherFilter").value); const entries = [...app.researchers.values()].filter((item) => normText(item.name).includes(q)).sort((a, b) => b.rows.length - a.rows.length).slice(0, 80); $("researcherResults").innerHTML = entries.length ? entries.map((researcher) => { const surveys = countBy(researcher.rows, (row) => row.surveyName); const statuses = countBy(researcher.rows, (row) => row.collectionStatus); const sampleGroups = groupForRows(researcher.rows).slice(0, 10); return `
${escapeHtml(researcher.name)}${researcher.rows.length} منشأة/سجل · ${surveys.size} مسح · ${sharedCount(researcher.rows)} منشأة متقاطعةعرض التفاصيل
${[...surveys.entries()].map(([name, count]) => metric(count, name)).join("")}

حالات الاستيفاء

${[...statuses.entries()].map(([status, count]) => `${escapeHtml(status)} · ${count}`).join("")}

نماذج من السجلات

${renderCompactEntities(sampleGroups)}
`; }).join("") : "
لا يوجد باحث مطابق.
"; } function renderSurveys() { $("surveyResults").innerHTML = [...app.surveys.entries()].map(([name, rows]) => { const researchers = countBy(rows, (row) => row.researcher); const statuses = countBy(rows, (row) => row.collectionStatus); const shared = sharedCount(rows); const sampleGroups = groupForRows(rows.filter((row) => row.commercialRegister && new Set(app.byRegister.get(row.commercialRegister).map((z) => z.surveyName)).size > 1)).slice(0, 8); return `
${escapeHtml(name)}${rows.length.toLocaleString("ar-SA")} سجل · ${researchers.size} باحث · ${shared} منشأة متقاطعةعرض التفاصيل
${metric(rows.length.toLocaleString("ar-SA"), "إجمالي السجلات")}${metric(researchers.size, "عدد الباحثين")}${metric(shared, "منشآت متقاطعة")}

حالات الاستيفاء في المسح

${[...statuses.entries()].map(([status, count]) => `${escapeHtml(status)} · ${count}`).join("")}

أعلى الباحثين حسب عدد السجلات

${[...researchers.entries()].sort((a, b) => b[1] - a[1]).slice(0, 8).map(([researcher, count]) => `${escapeHtml(researcher)} · ${count}`).join("")}

نماذج من التقاطعات

${sampleGroups.length ? renderCompactEntities(sampleGroups) : "
لا توجد تقاطعات في هذا المسح.
"}
`; }).join(""); } function escapeXml(v) { return String(v ?? "").replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } function xmlCell(value, style = "") { return `${escapeXml(value)}`; } function exportBatch() { if (!app.batch.length) { toast("لا توجد نتائج لتصديرها."); return; } const max = Math.max(0, ...app.batch.map((item) => item.group?.rows.length || 0)); const headers = ["رقم السجل التجاري", "اسم المنشأة", "عدد مرات الظهور", "حالة المطابقة"]; for (let i = 1; i <= max; i += 1) headers.push(`الباحث ${i}`, `المسح ${i}`, `حالة الاستيفاء ${i}`); const data = app.batch.map((item) => { const g = item.group; const values = [item.register, g?.name || "", g?.rows.length || 0, g ? (g.multiSurvey ? "موجود في عدة مسوح" : "موجود") : "غير موجود"]; const occurrences = g ? [...g.rows].sort((a, b) => a.surveyName.localeCompare(b.surveyName, "ar")) : []; for (let i = 0; i < max; i += 1) { const row = occurrences[i]; values.push(row?.researcher || "", row?.surveyName || "", row?.collectionStatus || ""); } return values; }); const xml = `${headers.map((h) => xmlCell(h, "h")).join("")}${data.map((row) => `${row.map((v) => xmlCell(v)).join("")}`).join("")}
11
`; const blob = new Blob(["\uFEFF" + xml], { type: "application/vnd.ms-excel;charset=utf-8" }); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = "نتائج_فحص_السجلات.xls"; a.click(); URL.revokeObjectURL(a.href); toast("تم إنشاء ملف Excel بنجاح."); } function openEntity(key) { const group = app.entityGroups.get(key); if (!group) return; showView("search"); $("q").value = group.commercialRegister || group.rows[0]?.facilityName || ""; $("searchResults").className = "results"; $("searchResults").innerHTML = renderEntity(group); $("searchMeta").textContent = "تفاصيل السجل المحدد."; } function debounce(fn, wait = 180) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), wait); }; } async function init() { setupLoginGate(); try { await loadData(); renderMetrics(); renderIntersections(); renderResearchers(); renderSurveys(); } catch (err) { console.error(err); document.querySelector("main").innerHTML = "
تعذر العثور على ملف بيانات المسوح.
"; return; } const debouncedSearch = debounce(search); document.querySelectorAll(".nav-link").forEach((button) => button.addEventListener("click", () => showView(button.dataset.view))); $("q").addEventListener("input", debouncedSearch); $("q").addEventListener("keydown", (event) => { if (event.key === "Enter") search(); }); $("btnClear").addEventListener("click", () => { $("q").value = ""; search(); $("q").focus(); }); $("batchInput").addEventListener("input", updateBatchHint); $("btnBatch").addEventListener("click", batchCheck); $("btnBatchClear").addEventListener("click", () => { $("batchInput").value = ""; app.batch = []; updateBatchHint(); $("batchSummary").innerHTML = ""; $("batchResults").className = "results empty-state"; $("batchResults").textContent = "الصق قائمة السجلات ثم اختر «فحص السجلات»."; $("btnExport").disabled = true; }); $("btnExport").addEventListener("click", exportBatch); $("researcherFilter").addEventListener("input", renderResearchers); document.addEventListener("click", (event) => { const button = event.target.closest("[data-entity]"); if (button) { openEntity(button.dataset.entity); return; } const closeDashboard = event.target.closest(".dashboard-close"); if (closeDashboard) { const panel = $("dashboardDetails"); panel.className = "dashboard-details empty-state"; panel.textContent = "اختر إحدى البطاقات لعرض التفاصيل هنا."; return; } const dashboardCard = event.target.closest("[data-dashboard-detail]"); if (dashboardCard) renderDashboardDetail(dashboardCard.dataset.dashboardDetail); }); document.addEventListener("keydown", (event) => { if (event.key !== "Enter" && event.key !== " ") return; const dashboardCard = event.target.closest("[data-dashboard-detail]"); if (!dashboardCard) return; event.preventDefault(); renderDashboardDetail(dashboardCard.dataset.dashboardDetail); }); } init();