|
|
| let COLS = []; |
|
|
| const qEl = document.getElementById("q"); |
| const btnSearch = document.getElementById("btnSearch"); |
| const btnClear = document.getElementById("btnClear"); |
| const resultsEl = document.getElementById("results"); |
| const totalChip = document.getElementById("totalChip"); |
| const countPill = document.getElementById("countPill"); |
| const summaryPill = document.getElementById("summaryPill"); |
|
|
| const FIELD_MAP = { |
| researcher: ["اسم الباحث", "اسم الباحث/ة", "الباحث", "الاسم", "K"], |
| facility: ["اسم المنشأة", "إسم المنشأة", "المنشأة", "اسم المنشاه", "C"], |
| description: ["الوصف", "الوصف.1", "S"], |
| completion: ["حالة اكتمال البيانات", "حالة الاكتمال", "U"], |
| collection: ["حالة الاستيفاء", "T"], |
| approval: ["الوصف.1", "حالة الاعتماد", "اعتماد", "الوصف"], |
| }; |
|
|
| const DISPLAY_COLUMNS = [ |
| { key: "researcher", label: "اسم الباحث" }, |
| { key: "facility", label: "اسم المنشأة" }, |
| { key: "collection", label: "حالة الاستيفاء" }, |
| { key: "completion", label: "حالة اكتمال البيانات" }, |
| { key: "description", label: "الوصف" }, |
| ]; |
|
|
| const RESOLVED = { |
| researcher: null, |
| facility: null, |
| description: null, |
| completion: null, |
| collection: null, |
| approval: null, |
| }; |
|
|
| function escHtml(value) { |
| return String(value ?? "") |
| .replace(/&/g, "&") |
| .replace(/</g, "<") |
| .replace(/>/g, ">") |
| .replace(/"/g, """) |
| .replace(/'/g, "'"); |
| } |
|
|
| function cleanHeader(value) { |
| return String(value ?? "") |
| .replace(/[\u200E\u200F\u202A-\u202E]/g, "") |
| .replace(/\u00A0/g, " ") |
| .replace(/\s+/g, " ") |
| .trim(); |
| } |
|
|
| function normalizeArabic(value) { |
| if (value == null) return ""; |
| return String(value) |
| .toLowerCase() |
| .trim() |
| .replace(/[إأآا]/g, "ا") |
| .replace(/ى/g, "ي") |
| .replace(/ة/g, "ه") |
| .replace(/[\u064B-\u065F\u0670]/g, "") |
| .replace(/\s+/g, " "); |
| } |
|
|
| function safeGet(obj, key) { |
| if (!obj || !key) return ""; |
| return Object.prototype.hasOwnProperty.call(obj, key) ? obj[key] : ""; |
| } |
|
|
| function findColumn(cols, wantedList) { |
| const wanted = Array.isArray(wantedList) ? wantedList : [wantedList]; |
| const cleanedCols = cols.map((col) => ({ |
| original: col, |
| clean: cleanHeader(col), |
| norm: normalizeArabic(cleanHeader(col)), |
| })); |
|
|
| for (const candidate of wanted) { |
| const cleanCandidate = cleanHeader(candidate); |
| const normCandidate = normalizeArabic(cleanCandidate); |
|
|
| let hit = cleanedCols.find((c) => c.clean === cleanCandidate); |
| if (hit) return hit.original; |
|
|
| hit = cleanedCols.find((c) => c.norm === normCandidate); |
| if (hit) return hit.original; |
|
|
| hit = cleanedCols.find((c) => c.clean.includes(cleanCandidate) || c.norm.includes(normCandidate)); |
| if (hit) return hit.original; |
| } |
|
|
| return null; |
| } |
|
|
| function tagClass(value) { |
| const v = normalizeArabic(value); |
|
|
| if ( |
| v.includes("تم") || |
| (v.includes("مكتمل") && !v.includes("غير")) || |
| (v.includes("مكتمله") && !v.includes("غير")) || |
| (v.includes("مستوفي") && !v.includes("غير")) |
| ) { |
| return "ok"; |
| } |
|
|
| if ( |
| v.includes("غير") || |
| v.includes("ناقص") || |
| v.includes("مرفوض") || |
| v.includes("تعذر") || |
| v.includes("لم يتم") |
| ) { |
| return "warn"; |
| } |
|
|
| return "info"; |
| } |
|
|
| function renderEmpty(message) { |
| resultsEl.innerHTML = `<div class="empty">${escHtml(message)}</div>`; |
| } |
|
|
| function resolveColumns() { |
| RESOLVED.researcher = findColumn(COLS, FIELD_MAP.researcher); |
| RESOLVED.facility = findColumn(COLS, FIELD_MAP.facility); |
| RESOLVED.description = findColumn(COLS, FIELD_MAP.description); |
| RESOLVED.completion = findColumn(COLS, FIELD_MAP.completion); |
| RESOLVED.collection = findColumn(COLS, FIELD_MAP.collection); |
| RESOLVED.approval = findColumn(COLS, FIELD_MAP.approval); |
| } |
|
|
| function resolveApprovalStatus(row) { |
| const direct = normalizeArabic(getDisplayValue(row, "approval")); |
| if (direct.includes("غير معتمد") || (direct.includes("معتمد") && direct.includes("غير"))) return "غير معتمد"; |
| if (direct === "معتمد" || (direct.includes("معتمد") && !direct.includes("غير"))) return "معتمد"; |
|
|
| const description = normalizeArabic(getDisplayValue(row, "description")); |
| if (description.includes("غير معتمد") || (description.includes("معتمد") && description.includes("غير"))) return "غير معتمد"; |
| if (description === "معتمد" || (description.includes("معتمد") && !description.includes("غير"))) return "معتمد"; |
|
|
| return ""; |
| } |
|
|
| function getDisplayValue(row, key) { |
| const col = RESOLVED[key]; |
| return safeGet(row, col); |
| } |
|
|
| function renderSummary(rows) { |
| const approved = rows.filter((row) => resolveApprovalStatus(row) === "معتمد").length; |
| const notApproved = rows.filter((row) => resolveApprovalStatus(row) === "غير معتمد").length; |
|
|
| summaryPill.innerHTML = [ |
| `<span class="tag info">إجمالي العينات: ${rows.length}</span>`, |
| `<span class="tag ok">معتمد: ${approved}</span>`, |
| `<span class="tag warn">غير معتمد: ${notApproved}</span>`, |
| ].join(" "); |
| } |
|
|
| function renderTable(rows) { |
| if (!rows.length) { |
| renderEmpty("لا توجد نتائج مطابقة."); |
| countPill.textContent = "النتائج: 0"; |
| summaryPill.textContent = "—"; |
| return; |
| } |
|
|
| countPill.textContent = `النتائج: ${rows.length}`; |
| renderSummary(rows); |
|
|
| const thead = ` |
| <thead> |
| <tr> |
| ${DISPLAY_COLUMNS.map((col) => `<th>${escHtml(col.label)}</th>`).join("")} |
| </tr> |
| </thead> |
| `; |
|
|
| const tbody = ` |
| <tbody> |
| ${rows.map((row) => ` |
| <tr> |
| ${DISPLAY_COLUMNS.map((col) => { |
| const value = getDisplayValue(row, col.key); |
| if (col.key === "collection" || col.key === "completion") { |
| return `<td><span class="tag ${tagClass(value)}">${escHtml(value || "—")}</span></td>`; |
| } |
| return `<td>${escHtml(value || "—")}</td>`; |
| }).join("")} |
| </tr> |
| `).join("")} |
| </tbody> |
| `; |
|
|
| resultsEl.innerHTML = `<div class="tableWrap"><table>${thead}${tbody}</table></div>`; |
| } |
|
|
| function doSearch() { |
| const q = normalizeArabic(qEl.value); |
|
|
| if (!q) { |
| renderEmpty("اكتب اسم الباحث ثم اضغط (بحث)."); |
| countPill.textContent = "النتائج: 0"; |
| summaryPill.textContent = "—"; |
| return; |
| } |
|
|
| if (!RESOLVED.researcher) { |
| renderEmpty("⚠️ لم يتم التعرف على عمود اسم الباحث."); |
| countPill.textContent = "النتائج: 0"; |
| summaryPill.textContent = "—"; |
| return; |
| } |
|
|
| const rows = DATA.filter((row) => normalizeArabic(getDisplayValue(row, "researcher")).includes(q)); |
| renderTable(rows); |
| } |
|
|
| function clearAll() { |
| qEl.value = ""; |
| renderEmpty("اكتب اسمك للبحث"); |
| countPill.textContent = "النتائج: 0"; |
| summaryPill.textContent = "—"; |
| qEl.focus(); |
| } |
|
|
| function validateRequiredColumns() { |
| const missing = DISPLAY_COLUMNS |
| .filter((col) => !RESOLVED[col.key]) |
| .map((col) => col.label); |
|
|
| return missing; |
| } |
|
|
| function init() { |
| if (!Array.isArray(DATA) || !DATA.length) { |
| totalChip.innerHTML = `إجمالي السجلات: <b>0</b>`; |
| renderEmpty("⚠️ لا توجد بيانات داخل ملف data.js"); |
| return; |
| } |
|
|
| COLS = Object.keys(DATA[0]).map(cleanHeader); |
| const originalCols = Object.keys(DATA[0]); |
| COLS = originalCols; |
| resolveColumns(); |
|
|
| totalChip.innerHTML = `إجمالي السجلات: <b>${DATA.length}</b>`; |
|
|
| const missing = validateRequiredColumns(); |
| if (missing.length) { |
| renderEmpty(`⚠️ تعذر العثور على الأعمدة التالية في البيانات:\n${missing.join("\n")}`); |
| return; |
| } |
|
|
| clearAll(); |
| } |
|
|
| btnSearch.addEventListener("click", doSearch); |
| btnClear.addEventListener("click", clearAll); |
| qEl.addEventListener("keydown", (ev) => { |
| if (ev.key === "Enter") doSearch(); |
| }); |
| qEl.addEventListener("input", () => { |
| const value = normalizeArabic(qEl.value); |
| if (value.length >= 2) doSearch(); |
| if (!qEl.value.trim()) clearAll(); |
| }); |
|
|
| init(); |