WPI / script.js
stat2025's picture
Update script.js
5d692ef verified
// عناصر الواجهة
const searchBox = document.getElementById("searchBox");
const clearSearch = document.getElementById("clearSearch");
const pageSizeEl = document.getElementById("pageSize");
const themeBtn = document.getElementById("themeToggle");
const tableBody = document.getElementById("tableBody");
const pagination = document.getElementById("pagination");
const counter = document.getElementById("counter");
const loading = document.getElementById("loading");
let data = [];
let filtered = [];
let currentPage = 1;
const DEFAULT_PAGE_SIZE = 10;
let rowsPerPage = DEFAULT_PAGE_SIZE;
// ضبط قيمة عدد الصفوف الافتراضي إن وُجد العنصر
if (pageSizeEl) {
pageSizeEl.value = String(DEFAULT_PAGE_SIZE);
}
/* ========================
دوال مساعدة للبحث بالعربية
======================== */
// توحيد الحروف العربية لتسهيل البحث
function normalizeArabic(str = "") {
return str
.replace(/[\u064B-\u0652]/g, "") // إزالة التشكيل
.replace(/[أإآا]/g, "ا")
.replace(/ى/g, "ي")
.replace(/ؤ/g, "و")
.replace(/ئ/g, "ي")
.replace(/ة/g, "ه")
.toLowerCase();
}
// التحقق إن كان النص يحتوي على عبارة البحث
function matches(text, query) {
if (!query) return true;
return normalizeArabic(String(text ?? "")).includes(
normalizeArabic(query)
);
}
// تمييز كلمة البحث داخل النص
function highlight(text, query) {
if (!query) return String(text ?? "");
const safe = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
return String(text ?? "").replace(
new RegExp(safe, "gi"),
(m) => `<mark class="hl">${m}</mark>`
);
}
/* ========================
الترقيم (Pagination)
======================== */
function renderPagination() {
pagination.innerHTML = "";
const totalRows = filtered.length;
const totalPages = Math.ceil(totalRows / rowsPerPage);
if (totalPages <= 1) return;
const makeBtn = (label, page, options = {}) => {
const btn = document.createElement("button");
btn.textContent = label;
btn.className = "page-btn" + (options.ghost ? " ghost" : "");
if (options.disabled) btn.disabled = true;
btn.addEventListener("click", () => {
currentPage = page;
updateView();
window.scrollTo({ top: 0, behavior: "smooth" });
});
return btn;
};
// السابق
pagination.appendChild(
makeBtn("‹", Math.max(1, currentPage - 1), {
ghost: true,
disabled: currentPage === 1,
})
);
// منطق إظهار أرقام الصفحات
const windowSize = 5;
let start = Math.max(1, currentPage - Math.floor(windowSize / 2));
let end = Math.min(totalPages, start + windowSize - 1);
if (end - start + 1 < windowSize) {
start = Math.max(1, end - windowSize + 1);
}
if (start > 1) {
pagination.appendChild(
makeBtn("1", 1, { ghost: currentPage !== 1 })
);
}
if (start > 2) {
const dots = document.createElement("span");
dots.className = "dots";
dots.textContent = "...";
pagination.appendChild(dots);
}
for (let p = start; p <= end; p++) {
const btn = makeBtn(String(p), p);
if (p === currentPage) btn.classList.add("active");
pagination.appendChild(btn);
}
if (end < totalPages - 1) {
const dots = document.createElement("span");
dots.className = "dots";
dots.textContent = "...";
pagination.appendChild(dots);
}
if (end < totalPages) {
pagination.appendChild(
makeBtn(String(totalPages), totalPages, {
ghost: currentPage !== totalPages,
})
);
}
// التالي
pagination.appendChild(
makeBtn("›", Math.min(totalPages, currentPage + 1), {
ghost: true,
disabled: currentPage === totalPages,
})
);
}
/* ========================
رسم الجدول (3 أعمدة)
======================== */
function renderTable() {
const query = searchBox ? searchBox.value.trim() : "";
tableBody.innerHTML = "";
const startIndex = (currentPage - 1) * rowsPerPage;
const pageItems = filtered.slice(startIndex, startIndex + rowsPerPage);
if (pageItems.length === 0) {
const tr = document.createElement("tr");
const td = document.createElement("td");
td.colSpan = 3;
td.className = "no-results";
td.textContent = "لا توجد نتائج مطابقة لبحثك.";
tr.appendChild(td);
tableBody.appendChild(tr);
return;
}
pageItems.forEach((item) => {
const tr = document.createElement("tr");
const nameTd = document.createElement("td");
nameTd.innerHTML = highlight(item.name, query);
const qtyTd = document.createElement("td");
qtyTd.innerHTML = highlight(item.quantity, query);
const unitTd = document.createElement("td");
unitTd.innerHTML = highlight(item.unit, query);
tr.appendChild(nameTd);
tr.appendChild(qtyTd);
tr.appendChild(unitTd);
tableBody.appendChild(tr);
});
}
/* ========================
تحديث العرض (فلترة + عداد)
======================== */
function updateView() {
const query = searchBox ? searchBox.value.trim() : "";
filtered = data.filter((item) => {
return (
matches(item.code, query) ||
matches(item.name, query) ||
matches(item.quantity, query) ||
matches(item.unit, query)
);
});
if (!counter) return;
if (!query) {
counter.textContent = `إجمالي البنود: ${data.length.toLocaleString()}`;
} else if (filtered.length) {
counter.textContent = `✅ عدد النتائج: ${filtered.length.toLocaleString()}`;
} else {
counter.textContent = "لا توجد نتائج مطابقة";
}
if (loading) {
loading.style.display = "none";
}
renderTable();
renderPagination();
}
/* ========================
أدوات مساعدة
======================== */
function debounce(fn, delay = 220) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), delay);
};
}
/* ========================
ربط الأحداث
======================== */
// البحث الفوري
if (searchBox) {
searchBox.addEventListener(
"input",
debounce(() => {
currentPage = 1;
updateView();
})
);
}
// زر مسح البحث
if (clearSearch) {
clearSearch.addEventListener("click", () => {
searchBox.value = "";
searchBox.focus();
currentPage = 1;
updateView();
});
}
// تغيير عدد الصفوف
if (pageSizeEl) {
pageSizeEl.addEventListener("change", () => {
const val = parseInt(pageSizeEl.value, 10);
rowsPerPage = !isNaN(val) && val > 0 ? val : DEFAULT_PAGE_SIZE;
currentPage = 1;
updateView();
});
}
// اختصار / للتركيز على البحث
document.addEventListener("keydown", (e) => {
if (e.key === "/") {
if (document.activeElement === searchBox) return;
e.preventDefault();
if (searchBox) searchBox.focus();
}
});
/* ========================
الثيم (فاتح / ليلي)
======================== */
(function initTheme() {
if (!themeBtn) return;
const saved = localStorage.getItem("theme") || "light";
const root = document.documentElement;
if (saved === "dark") {
root.classList.add("dark");
themeBtn.textContent = "☀️ فاتح";
} else {
root.classList.remove("dark");
themeBtn.textContent = "ليلي";
}
themeBtn.addEventListener("click", () => {
const isDark = root.classList.toggle("dark");
localStorage.setItem("theme", isDark ? "dark" : "light");
themeBtn.textContent = isDark ? "☀️ فاتح" : "ليلي";
});
})();
/* ========================
تحميل data.json
======================== */
fetch("data.json", { cache: "no-cache" })
.then((res) => res.json())
.then((json) => {
// توحيد الحقول + دعم أي تسميات قديمة
data = (Array.isArray(json) ? json : []).map((row) => ({
code:
row.code ??
row.Code ??
row["رمز البند"] ??
"",
name:
row.name ??
row["اسم البند"] ??
"",
quantity:
row.quantity ??
row["الكمية المطلوبة"] ??
"",
unit:
row.unit ??
row["وحدة البند"] ??
"",
}));
currentPage = 1;
updateView();
})
.catch((err) => {
console.error("تعذر تحميل data.json:", err);
if (loading) {
loading.textContent = "تعذر تحميل البيانات ❌";
}
});
/* ========================
تأثير بسيط لحالة التمرير
======================== */
(function attachScrollHint() {
const wrap = document.querySelector(".table-wrap");
if (!wrap) return;
let timer;
wrap.addEventListener("scroll", () => {
wrap.classList.add("scrolling");
clearTimeout(timer);
timer = setTimeout(() => {
wrap.classList.remove("scrolling");
}, 200);
});
})();