/* طبقة البيانات والفهرسة: تبقى كل المعالجة محلية داخل المتصفح. */
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("السجل التجاري", `${escapeHtml(group.commercialRegister)} `)}${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("السجل التجاري", `${escapeHtml(group.commercialRegister)} `)}${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("رقم السجل التجاري", `${escapeHtml(item.register)} `)}${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("السجل التجاري", `${escapeHtml(group.commercialRegister)} `)}${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("السجل التجاري", `${escapeHtml(group.commercialRegister || "-")} `)}${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("")}
1 1 `;
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();