MDN / script.js
stat2025's picture
Upload 3 files
609c7e4 verified
const FIELD_MAP = {
name: "إسم المنشأة",
phone: "رقم التواصل",
email: "البريد الالكتروني",
city: "المدينة الصناعية",
commercialRegister: "السجل التجاري من الاطار",
activityCode: "كود النشاط",
activity: "النشاط"
};
const ICONS = {
phone: `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2a1.3 1.3 0 0 1 1.34-.31c1.47.49 3.04.75 4.65.75.72 0 1.3.58 1.3 1.3v3.48c0 .72-.58 1.3-1.3 1.3C10.52 21.7 1.8 12.98 1.8 2.1 1.8 1.38 2.38.8 3.1.8h3.5c.72 0 1.3.58 1.3 1.3 0 1.62.26 3.18.75 4.65.14.47.03.99-.32 1.33l-1.71 1.71Z" /></svg>`,
whatsapp: `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12.04 2.2a9.7 9.7 0 0 0-8.39 14.56L2.6 21.8l5.17-1.23a9.67 9.67 0 0 0 4.27.99h.01a9.68 9.68 0 0 0 0-19.36Zm5.7 13.65c-.24.68-1.39 1.3-1.95 1.38-.5.08-1.14.12-1.84-.12-.43-.14-.98-.32-1.68-.63-2.96-1.28-4.88-4.26-5.03-4.46-.15-.2-1.2-1.59-1.2-3.04s.76-2.16 1.03-2.45c.27-.3.58-.37.78-.37h.56c.18.01.42-.07.66.5.24.58.82 2 .89 2.15.07.15.12.32.02.52-.1.2-.15.32-.3.49-.15.17-.32.38-.45.51-.15.15-.3.31-.13.61.17.3.75 1.24 1.61 2.01 1.1.98 2.03 1.28 2.33 1.43.3.15.47.13.64-.08.17-.2.74-.86.94-1.15.2-.3.4-.25.66-.15.27.1 1.72.81 2.01.96.3.15.5.22.57.35.07.12.07.72-.17 1.4Z" /></svg>`
};
const queryInput = document.getElementById("query");
const resultsEl = document.getElementById("results");
const clearButton = document.getElementById("clearButton");
const emptyTemplate = document.getElementById("emptyTemplate");
function escapeHtml(value) {
return String(value ?? "")
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#039;");
}
function normalizeArabic(value) {
return String(value || "")
.toLowerCase()
.replace(/[إأآا]/g, "ا")
.replace(/ى/g, "ي")
.replace(/ة/g, "ه")
.replace(/ؤ/g, "و")
.replace(/ئ/g, "ي")
.replace(/[ً-ٰٟ]/g, "")
.replace(/\s+/g, " ")
.trim();
}
function digitsOnly(value) {
return String(value || "").replace(/\D/g, "");
}
function formatPhoneDisplay(value) {
const digits = digitsOnly(value);
if (!digits || digits === "0") return "غير متاح";
if (digits.length === 9 && digits.startsWith("5")) return `0${digits}`;
return digits;
}
function hasUsefulValue(value) {
const text = String(value ?? "").trim();
if (!text) return false;
const normalized = text.replace(/\s+/g, "").replace(/[ـ-]/g, "");
if (!normalized) return false;
if (/^0+$/.test(digitsOnly(normalized)) && !/[^\d\s+()-]/.test(normalized)) return false;
return !["غيرمتاح", "لايوجد", "لاينطبق", "nan", "null", "undefined"].includes(normalizeArabic(normalized));
}
function toSaudiInternational(value) {
let digits = digitsOnly(value);
if (!digits || digits === "0") return "";
if (digits.startsWith("00")) digits = digits.slice(2);
if (digits.startsWith("+")) digits = digits.slice(1);
if (digits.startsWith("966")) return digits;
if (digits.startsWith("0") && digits.length >= 9) return `966${digits.slice(1)}`;
if (digits.startsWith("5") && digits.length === 9) return `966${digits}`;
return digits.length >= 7 ? digits : "";
}
function highlightName(name, query) {
const original = String(name || "");
const q = String(query || "").trim();
if (!original || !q) return escapeHtml(original || "-");
const index = original.toLowerCase().indexOf(q.toLowerCase());
if (index === -1) return escapeHtml(original);
const before = escapeHtml(original.slice(0, index));
const match = escapeHtml(original.slice(index, index + q.length));
const after = escapeHtml(original.slice(index + q.length));
return `${before}<mark>${match}</mark>${after}`;
}
function field(label, value, options = {}) {
if (!hasUsefulValue(value)) return "";
const display = value && String(value).trim() ? value : "-";
const content = options.html || escapeHtml(display);
const classes = ["info-field", options.full ? "full" : ""].filter(Boolean).join(" ");
const valueClasses = ["value", options.ltr ? "ltr" : ""].filter(Boolean).join(" ");
const action = options.action || "";
return `
<div class="${classes}">
<span class="label">${escapeHtml(label)}</span>
<div class="field-line">
<div class="${valueClasses}">${content}</div>
${action}
</div>
</div>
`;
}
function whatsappAction(phone) {
const normalized = toSaudiInternational(phone);
if (!normalized) return "";
const disabled = !normalized;
const whatsHref = disabled ? "#" : `https://wa.me/${normalized}`;
const disabledClass = disabled ? " disabled" : "";
return `
<a class="inline-whatsapp${disabledClass}" href="${whatsHref}" target="_blank" rel="noopener" title="واتساب" aria-label="واتساب">${ICONS.whatsapp}</a>
`;
}
function renderEmpty() {
const node = emptyTemplate.content.cloneNode(true);
resultsEl.replaceChildren(node);
}
function renderResults(items, query) {
if (!query.trim()) {
renderEmpty();
return;
}
if (!items.length) {
renderEmpty();
return;
}
resultsEl.innerHTML = items.map((item) => {
const name = item[FIELD_MAP.name] || "-";
const phone = item[FIELD_MAP.phone] || "";
const email = item[FIELD_MAP.email] || "";
const city = item[FIELD_MAP.city] || "";
const emailHtml = hasUsefulValue(email) ? `<a href="mailto:${escapeHtml(email)}">${escapeHtml(email)}</a>` : "";
return `
<article class="result-card">
<div class="card-head">
<div>
<h2 class="entity-title">${highlightName(name, query)}</h2>
${hasUsefulValue(city) ? `<p class="entity-meta">${escapeHtml(city)}</p>` : ""}
</div>
</div>
<div class="card-body">
${field("رقم التواصل", phone, { html: escapeHtml(formatPhoneDisplay(phone)), ltr: true, action: whatsappAction(phone) })}
${field("البريد الالكتروني", email, { html: emailHtml, ltr: true })}
${field("السجل التجاري", item[FIELD_MAP.commercialRegister], { ltr: true })}
${field("كود النشاط", item[FIELD_MAP.activityCode], { ltr: true })}
${field("المدينة الصناعية", city)}
${field("النشاط", item[FIELD_MAP.activity], { full: true })}
</div>
</article>
`;
}).join("");
}
function search() {
const query = queryInput.value.trim();
const normalizedQuery = normalizeArabic(query);
const matches = DATA.filter((item) => {
const name = normalizeArabic(item[FIELD_MAP.name]);
return normalizedQuery && name.includes(normalizedQuery);
}).sort((a, b) => String(a[FIELD_MAP.name] || "").localeCompare(String(b[FIELD_MAP.name] || ""), "ar"));
renderResults(matches, query);
}
queryInput.addEventListener("input", search);
queryInput.addEventListener("keydown", (event) => {
if (event.key === "Enter") search();
});
clearButton.addEventListener("click", () => {
queryInput.value = "";
queryInput.focus();
renderEmpty();
});
renderEmpty();