"use strict"; const loginView = document.getElementById("loginView"); const dashboardView = document.getElementById("dashboardView"); const loginForm = document.getElementById("loginForm"); const passwordInput = document.getElementById("password"); const loginButton = document.getElementById("loginButton"); const loginError = document.getElementById("loginError"); const togglePassword = document.getElementById("togglePassword"); const logoutButton = document.getElementById("logoutButton"); const regionSelect = document.getElementById("regionSelect"); const regionButtons = document.getElementById("regionButtons"); const citySelect = document.getElementById("citySelect"); const searchInput = document.getElementById("searchInput"); const clearFiltersButton = document.getElementById("clearFiltersButton"); const emptyState = document.getElementById("emptyState"); const resultsSection = document.getElementById("resultsSection"); const resultsMeta = document.getElementById("resultsMeta"); const resultsTitle = document.getElementById("resultsTitle"); const samplesGrid = document.getElementById("samplesGrid"); const noResults = document.getElementById("noResults"); const loadMoreWrap = document.getElementById("loadMoreWrap"); const loadMoreButton = document.getElementById("loadMoreButton"); const loadMoreMeta = document.getElementById("loadMoreMeta"); const toast = document.getElementById("toast"); const PAGE_SIZE = 24; const STATE_KEY = "icsInquiryFilters"; const CONTACT_MESSAGE = (row) => [ "السلام عليكم ورحمة الله وبركاته", "", "أستاذي الكريم،", "نأمل تزويدنا بموقع المنشأة التالية، وذلك لاستكمال بيانات الزيارة الميدانية:", "", `منشأة: ${row.establishmentName || "-"}`, `السجل التجاري: ${row.commercialRecord || "-"}`, `المدينة: ${row.city || "-"}`, "", "شاكرين لكم تعاونكم.", ].join("\n"); let rows = []; let visibleRows = []; let renderedCount = 0; let toastTimer; let isRestoringState = false; function base64ToBytes(value) { const binary = atob(value); return Uint8Array.from(binary, (char) => char.charCodeAt(0)); } async function deriveKey(password, salt) { const material = await crypto.subtle.importKey( "raw", new TextEncoder().encode(password), "PBKDF2", false, ["deriveKey"], ); return crypto.subtle.deriveKey( { name: "PBKDF2", salt, iterations: ENCRYPTED_DATA.iterations, hash: "SHA-256" }, material, { name: "AES-GCM", length: 256 }, false, ["decrypt"], ); } async function decryptData(password) { const salt = base64ToBytes(ENCRYPTED_DATA.salt); const iv = base64ToBytes(ENCRYPTED_DATA.iv); const cipherText = base64ToBytes(ENCRYPTED_DATA.payload); const key = await deriveKey(password, salt); const plainBuffer = await crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, cipherText); return JSON.parse(new TextDecoder().decode(plainBuffer)); } function normalize(value) { return String(value ?? "") .normalize("NFKD") .replace(/[\u064B-\u065F\u0670]/g, "") .replace(/[أإآ]/g, "ا") .replace(/ى/g, "ي") .replace(/ة/g, "ه") .replace(/\s+/g, " ") .trim() .toLowerCase(); } function uniqueSorted(values) { return [...new Set(values.filter(Boolean))].sort((a, b) => String(a).localeCompare(String(b), "ar", { sensitivity: "base" }), ); } function setOptions(select, values, placeholder) { select.replaceChildren(new Option(placeholder, "")); values.forEach((value) => select.add(new Option(value, value))); } function saveFilterState() { if (isRestoringState) return; localStorage.setItem( STATE_KEY, JSON.stringify({ region: regionSelect.value, city: citySelect.value, search: searchInput.value, }), ); } function getSavedFilterState() { try { return JSON.parse(localStorage.getItem(STATE_KEY) || "{}"); } catch { return {}; } } function updateRegionButtonState() { regionButtons.querySelectorAll(".region-choice").forEach((button) => { const isActive = button.dataset.region === regionSelect.value; button.classList.toggle("active", isActive); button.setAttribute("aria-checked", String(isActive)); }); } function setRegion(region, scrollToControls = false) { regionSelect.value = region; updateRegionButtonState(); const cities = uniqueSorted(rows.filter((row) => row.region === region).map((row) => row.city)); setOptions(citySelect, cities, "اختر المدينة الصناعية"); citySelect.disabled = !region; citySelect.value = ""; applyFilters(); if (scrollToControls) { window.scrollTo({ top: document.querySelector(".controls-panel").offsetTop - 16, behavior: "smooth" }); } } function showToast(message) { clearTimeout(toastTimer); toast.textContent = message; toast.classList.add("show"); toastTimer = setTimeout(() => toast.classList.remove("show"), 2200); } async function copyText(text, successMessage) { try { await navigator.clipboard.writeText(text); } catch { const area = document.createElement("textarea"); area.value = text; area.style.position = "fixed"; area.style.opacity = "0"; document.body.append(area); area.select(); document.execCommand("copy"); area.remove(); } showToast(successMessage); } function phoneDigits(value) { const match = String(value ?? "").match(/(?:\+?966|0)?5\d{8}/); if (!match) return ""; let digits = match[0].replace(/\D/g, ""); if (digits.startsWith("966")) return digits; if (digits.startsWith("0")) return `966${digits.slice(1)}`; return `966${digits}`; } function localPhoneNumber(value) { const digits = phoneDigits(value); if (!digits) return ""; return digits.startsWith("966") ? `0${digits.slice(3)}` : digits; } function isCoordinate(value) { return /^[-+]?\d{1,2}(?:\.\d+)?\s*,\s*[-+]?\d{1,3}(?:\.\d+)?$/.test(String(value ?? "").trim()); } function hasLocationInfo(row) { return Boolean(String(row.coordinates ?? "").trim()); } function sortRowsByLocationInfo(items) { return [...items].sort((a, b) => Number(hasLocationInfo(b)) - Number(hasLocationInfo(a))); } function createSvgIcon(path) { const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("viewBox", "0 0 24 24"); svg.setAttribute("aria-hidden", "true"); const p = document.createElementNS("http://www.w3.org/2000/svg", "path"); p.setAttribute("d", path); svg.append(p); return svg; } function createActionLink(label, href, iconPath) { const link = document.createElement("a"); link.className = "action-link"; link.href = href; link.target = "_blank"; link.rel = "noopener"; link.append(createSvgIcon(iconPath), document.createTextNode(label)); return link; } function createDetail(label, value, extraClass = "") { const item = document.createElement("div"); item.className = `detail-item ${extraClass}`.trim(); const title = document.createElement("span"); title.className = "detail-label"; title.textContent = label; const text = document.createElement("span"); text.className = "detail-value"; text.textContent = String(value); item.append(title, text); return item; } function createCoordinatesBlock(row) { const value = String(row.coordinates ?? "").trim(); if (!value) return null; const item = document.createElement("div"); const hasCoordinates = isCoordinate(value); item.className = `detail-item coordinates-detail ${hasCoordinates ? "map-detail" : "statement-detail"}`; const label = document.createElement("span"); label.className = "detail-label"; label.textContent = hasCoordinates ? "الإحداثيات" : "إفادة مدن"; item.append(label); if (hasCoordinates) { item.append( createActionLink( "اضغط هنا للذهاب بخرائط Google", `https://www.google.com/maps?q=${encodeURIComponent(value)}`, "M12 21s7-4.6 7-11a7 7 0 1 0-14 0c0 6.4 7 11 7 11Z M12 12.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5Z", ), ); return item; } const phone = phoneDigits(value); const note = document.createElement("p"); note.className = "contact-note"; note.textContent = value; item.append(note); if (phone) { const message = CONTACT_MESSAGE(row); const actions = document.createElement("div"); actions.className = "contact-actions"; const copyPhoneButton = document.createElement("button"); copyPhoneButton.type = "button"; copyPhoneButton.className = "action-link"; copyPhoneButton.append( createSvgIcon("M8 8h11v11H8z M5 16H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"), document.createTextNode("نسخ الرقم"), ); copyPhoneButton.addEventListener("click", () => copyText(localPhoneNumber(value), "تم نسخ الرقم"), ); actions.append( copyPhoneButton, createActionLink("واتساب", `https://wa.me/${phone}?text=${encodeURIComponent(message)}`, "M20.5 11.5a8.5 8.5 0 0 1-12.6 7.4L3 20l1.2-4.7A8.5 8.5 0 1 1 20.5 11.5Z M8.6 8.7c.2 3.3 2.7 5.8 6 6.2"), ); const copyButton = document.createElement("button"); copyButton.type = "button"; copyButton.className = "action-link"; copyButton.append(createSvgIcon("M8 8h11v11H8z M5 16H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"), document.createTextNode("نسخ رسالة مقترحة")); copyButton.addEventListener("click", () => copyText(message, "تم نسخ الرسالة المقترحة")); actions.append(copyButton); item.append(actions); } return item; } function createCard(row, index) { const card = document.createElement("article"); card.className = "sample-card"; const header = document.createElement("div"); header.className = "sample-card-header"; const heading = document.createElement("div"); const indexEl = document.createElement("span"); indexEl.className = "sample-index"; indexEl.textContent = `منشأة ${index + 1}`; const title = document.createElement("h3"); title.textContent = row.establishmentName || "منشأة دون اسم"; heading.append(indexEl, title); const badge = document.createElement("span"); badge.className = "status-badge"; badge.textContent = row.complianceStatus || "غير محدد"; header.append(heading, badge); const details = document.createElement("div"); details.className = "sample-details"; [ ["السجل التجاري", row.commercialRecord, "ltr-value wide-detail"], ["توضيح المدينة", row.cityClarification, "wide-detail"], ].forEach(([label, value, className]) => { if (String(value ?? "").trim()) details.append(createDetail(label, value, className)); }); const coordinatesBlock = createCoordinatesBlock(row); if (coordinatesBlock) details.append(coordinatesBlock); card.append(header, details); return card; } function renderVisibleRows(reset = false) { if (reset) { renderedCount = 0; samplesGrid.replaceChildren(); } const fragment = document.createDocumentFragment(); visibleRows.slice(renderedCount, renderedCount + PAGE_SIZE).forEach((row, offset) => { fragment.append(createCard(row, renderedCount + offset)); }); samplesGrid.append(fragment); renderedCount += Math.min(PAGE_SIZE, visibleRows.length - renderedCount); const remaining = visibleRows.length - renderedCount; loadMoreWrap.hidden = remaining <= 0; loadMoreButton.textContent = `عرض المزيد (${Math.min(PAGE_SIZE, remaining)})`; loadMoreMeta.textContent = `تم عرض ${renderedCount} من أصل ${visibleRows.length} منشأة`; } function applyFilters() { const region = regionSelect.value; const city = citySelect.value; const query = normalize(searchInput.value); const scopedRows = rows.filter((row) => { const matchesRegion = !region || row.region === region; const matchesCity = !city || row.city === city; return matchesRegion && matchesCity; }); visibleRows = sortRowsByLocationInfo(scopedRows.filter((row) => { if (!query) return Boolean(city || region); return ( normalize(row.establishmentName).includes(query) || normalize(row.commercialRecord).includes(query) || normalize(row.cityClarification).includes(query) ); })); const shouldShow = visibleRows.length > 0 || city || region || query; emptyState.hidden = shouldShow; resultsSection.hidden = !shouldShow; noResults.hidden = visibleRows.length !== 0; resultsTitle.textContent = city || region || "نتائج البحث"; resultsMeta.textContent = `${visibleRows.length} من أصل ${scopedRows.length} منشأة`; renderVisibleRows(true); saveFilterState(); } function initializeDashboard(data) { rows = data.rows || []; const regions = data.regions || uniqueSorted(rows.map((row) => row.region)); setOptions(regionSelect, regions, "اختر المنطقة"); const regionCounts = new Map(); rows.forEach((row) => regionCounts.set(row.region, (regionCounts.get(row.region) || 0) + 1)); regionButtons.replaceChildren( ...regions.map((region) => { const button = document.createElement("button"); button.type = "button"; button.className = "region-choice"; button.dataset.region = region; button.setAttribute("role", "radio"); button.setAttribute("aria-checked", "false"); const name = document.createElement("span"); name.textContent = region; const count = document.createElement("strong"); count.textContent = regionCounts.get(region) || 0; button.append(name, count); button.addEventListener("click", () => setRegion(region)); return button; }), ); setOptions(citySelect, [], "اختر المدينة الصناعية"); citySelect.disabled = true; restoreFilterState(); } function restoreFilterState() { const saved = getSavedFilterState(); isRestoringState = true; searchInput.value = saved.search || ""; if (saved.region && [...regionSelect.options].some((option) => option.value === saved.region)) { setRegion(saved.region); if (saved.city && [...citySelect.options].some((option) => option.value === saved.city)) { citySelect.value = saved.city; } } else { setRegion(""); } isRestoringState = false; applyFilters(); } function performLogout() { rows = []; visibleRows = []; searchInput.value = ""; setRegion(""); citySelect.value = ""; dashboardView.hidden = true; loginView.hidden = false; passwordInput.focus(); } loginForm.addEventListener("submit", async (event) => { event.preventDefault(); const password = passwordInput.value.trim(); if (!password) return; loginButton.disabled = true; loginButton.querySelector("span").textContent = "جاري التحقق..."; loginError.textContent = ""; try { const data = await decryptData(password); initializeDashboard(data); passwordInput.value = ""; loginView.hidden = true; dashboardView.hidden = false; } catch { loginError.textContent = "رمز الدخول غير صحيح."; passwordInput.select(); } finally { loginButton.disabled = false; loginButton.querySelector("span").textContent = "دخول"; } }); togglePassword.addEventListener("click", () => { const showing = passwordInput.type === "text"; passwordInput.type = showing ? "password" : "text"; togglePassword.setAttribute("aria-label", showing ? "إظهار رمز الدخول" : "إخفاء رمز الدخول"); }); logoutButton.addEventListener("click", performLogout); regionSelect.addEventListener("change", () => setRegion(regionSelect.value)); citySelect.addEventListener("change", applyFilters); searchInput.addEventListener("input", applyFilters); loadMoreButton.addEventListener("click", () => renderVisibleRows()); clearFiltersButton.addEventListener("click", () => { searchInput.value = ""; localStorage.removeItem(STATE_KEY); setRegion(""); showToast("تم مسح الاختيارات"); }); if (!window.crypto?.subtle || typeof ENCRYPTED_DATA === "undefined") { loginButton.disabled = true; loginError.textContent = "تعذر تشغيل التشفير في هذا المتصفح. استخدم متصفحًا حديثًا."; }